1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Alexander Larsson <alexl@redhat.com>
21  */
22 
23 #include <config.h>
24 
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <stdlib.h>
32 
33 #include <glib/gstdio.h>
34 #include <glib/gi18n.h>
35 #include <gio/gio.h>
36 
37 #include "gvfsbackendsmbbrowse.h"
38 #include "gvfsjobmountmountable.h"
39 #include "gvfsjobopenforread.h"
40 #include "gvfsjobread.h"
41 #include "gvfsjobseekread.h"
42 #include "gvfsjobqueryinfo.h"
43 #include "gvfsjobenumerate.h"
44 #include "gvfsdaemonprotocol.h"
45 #include "gvfskeyring.h"
46 #include "gmounttracker.h"
47 #include "gvfsbackendsmbprivate.h"
48 #include "gvfsutils.h"
49 
50 #include <libsmbclient.h>
51 
52 /* The magic "default workgroup" hostname */
53 #define DEFAULT_WORKGROUP_NAME "X-GNOME-DEFAULT-WORKGROUP"
54 
55 /* Time in seconds before we mark dirents cache outdated */
56 #define DEFAULT_CACHE_EXPIRATION_TIME 10
57 
58 typedef struct {
59   unsigned int smbc_type;
60   char *name;
61   char *name_normalized;
62   char *name_utf8;
63   char *comment;
64 } BrowseEntry;
65 
66 struct _GVfsBackendSmbBrowse
67 {
68   GVfsBackend parent_instance;
69 
70   char *user;
71   char *domain;
72   char *server;
73   char *mounted_server; /* server or DEFAULT_WORKGROUP_NAME */
74   char *default_workgroup;
75   int port;
76   SMBCCTX *smb_context;
77 
78   char *last_user;
79   char *last_domain;
80   char *last_password;
81 
82   GMountSource *mount_source;
83   int mount_try;
84   gboolean mount_try_again;
85   gboolean mount_cancelled;
86 
87   gboolean password_in_keyring;
88   GPasswordSave password_save;
89 
90   GMutex entries_lock;
91   GMutex update_cache_lock;
92   time_t last_entry_update;
93   GList *entries;
94 };
95 
96 
97 static GHashTable *server_cache = NULL;
98 static GMountTracker *mount_tracker = NULL;
99 
100 typedef struct {
101   char *server_name;
102   char *share_name;
103   char *domain;
104   char *username;
105 } CachedServer;
106 
G_DEFINE_TYPE(GVfsBackendSmbBrowse,g_vfs_backend_smb_browse,G_VFS_TYPE_BACKEND)107 G_DEFINE_TYPE (GVfsBackendSmbBrowse, g_vfs_backend_smb_browse, G_VFS_TYPE_BACKEND)
108 
109 static char *
110 normalize_smb_name_helper (const char *name, gssize len, gboolean valid_utf8)
111 {
112   if (valid_utf8)
113     return g_utf8_casefold (name, len);
114   else
115     return g_ascii_strdown (name, len);
116 }
117 
118 static char *
normalize_smb_name(const char * name,gssize len)119 normalize_smb_name (const char *name, gssize len)
120 {
121   gboolean valid_utf8;
122 
123   valid_utf8 = g_utf8_validate (name, len, NULL);
124   return normalize_smb_name_helper (name, len, valid_utf8);
125 }
126 
127 static char *
smb_name_to_utf8(const char * name,gboolean * valid_utf8_out)128 smb_name_to_utf8 (const char *name, gboolean *valid_utf8_out)
129 {
130   GString *string;
131   const gchar *remainder, *invalid;
132   gint remaining_bytes, valid_bytes;
133   gboolean valid_utf8;
134 
135   remainder = name;
136   remaining_bytes = strlen (name);
137   valid_utf8 = TRUE;
138 
139   string = g_string_sized_new (remaining_bytes);
140   while (remaining_bytes != 0)
141     {
142       if (g_utf8_validate (remainder, remaining_bytes, &invalid))
143 	break;
144       valid_utf8 = FALSE;
145 
146       valid_bytes = invalid - remainder;
147 
148       g_string_append_len (string, remainder, valid_bytes);
149       /* append U+FFFD REPLACEMENT CHARACTER */
150       g_string_append (string, "\357\277\275");
151 
152       remaining_bytes -= valid_bytes + 1;
153       remainder = invalid + 1;
154     }
155 
156   g_string_append (string, remainder);
157 
158   if (valid_utf8_out)
159     *valid_utf8_out = valid_utf8;
160 
161   return g_string_free (string, FALSE);
162 }
163 
164 static void
browse_entry_free(BrowseEntry * entry)165 browse_entry_free (BrowseEntry *entry)
166 {
167   g_free (entry->name);
168   g_free (entry->comment);
169   g_free (entry);
170 }
171 
172 static gboolean
cached_server_equal(gconstpointer _a,gconstpointer _b)173 cached_server_equal (gconstpointer  _a,
174 		     gconstpointer  _b)
175 {
176   const CachedServer *a = _a;
177   const CachedServer *b = _b;
178 
179   return
180     strcmp (a->server_name, b->server_name) == 0 &&
181     strcmp (a->share_name, b->share_name) == 0 &&
182     strcmp (a->domain, b->domain) == 0 &&
183     strcmp (a->username, b->username) == 0;
184 }
185 
186 static guint
cached_server_hash(gconstpointer key)187 cached_server_hash (gconstpointer key)
188 {
189   const CachedServer *server = key;
190 
191   return
192     g_str_hash (server->server_name) ^
193     g_str_hash (server->share_name) ^
194     g_str_hash (server->domain) ^
195     g_str_hash (server->username);
196 }
197 
198 static void
cached_server_free(CachedServer * server)199 cached_server_free (CachedServer *server)
200 {
201   g_free (server->server_name);
202   g_free (server->share_name);
203   g_free (server->domain);
204   g_free (server->username);
205   g_free (server);
206 }
207 
208 static void
g_vfs_backend_smb_browse_finalize(GObject * object)209 g_vfs_backend_smb_browse_finalize (GObject *object)
210 {
211   GVfsBackendSmbBrowse *backend;
212 
213   backend = G_VFS_BACKEND_SMB_BROWSE (object);
214 
215   g_free (backend->user);
216   g_free (backend->domain);
217   g_free (backend->mounted_server);
218   g_free (backend->server);
219   g_free (backend->default_workgroup);
220 
221   g_mutex_clear (&backend->entries_lock);
222   g_mutex_clear (&backend->update_cache_lock);
223 
224   smbc_free_context (backend->smb_context, TRUE);
225 
226   g_list_free_full (backend->entries, (GDestroyNotify)browse_entry_free);
227 
228   if (G_OBJECT_CLASS (g_vfs_backend_smb_browse_parent_class)->finalize)
229     (*G_OBJECT_CLASS (g_vfs_backend_smb_browse_parent_class)->finalize) (object);
230 }
231 
232 static void
g_vfs_backend_smb_browse_init(GVfsBackendSmbBrowse * backend)233 g_vfs_backend_smb_browse_init (GVfsBackendSmbBrowse *backend)
234 {
235   char *workgroup;
236   GSettings *settings;
237 
238   g_mutex_init (&backend->entries_lock);
239   g_mutex_init (&backend->update_cache_lock);
240 
241   if (mount_tracker == NULL)
242     mount_tracker = g_mount_tracker_new (NULL, FALSE);
243 
244   /* Get default workgroup name */
245   settings = g_settings_new ("org.gnome.system.smb");
246 
247   workgroup = g_settings_get_string (settings, "workgroup");
248   if (workgroup && workgroup[0])
249     backend->default_workgroup = workgroup;
250   else
251     g_free (workgroup);
252 
253   g_object_unref (settings);
254 
255   g_debug ("g_vfs_backend_smb_browse_init: default workgroup = '%s'\n", backend->default_workgroup ? backend->default_workgroup : "NULL");
256 }
257 
258 /**
259  * Authentication callback function type (method that includes context)
260  *
261  * Type for the the authentication function called by the library to
262  * obtain authentication credentals
263  *
264  * @param context   Pointer to the smb context
265  * @param srv       Server being authenticated to
266  * @param shr       Share being authenticated to
267  * @param wg        Pointer to buffer containing a "hint" for the
268  *                  workgroup to be authenticated.  Should be filled in
269  *                  with the correct workgroup if the hint is wrong.
270  * @param wglen     The size of the workgroup buffer in bytes
271  * @param un        Pointer to buffer containing a "hint" for the
272  *                  user name to be use for authentication. Should be
273  *                  filled in with the correct workgroup if the hint is
274  *                  wrong.
275  * @param unlen     The size of the username buffer in bytes
276  * @param pw        Pointer to buffer containing to which password
277  *                  copied
278  * @param pwlen     The size of the password buffer in bytes
279  *
280  */
281 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)282 auth_callback (SMBCCTX *context,
283 	       const char *server_name, const char *share_name,
284 	       char *domain_out, int domainmaxlen,
285 	       char *username_out, int unmaxlen,
286 	       char *password_out, int pwmaxlen)
287 {
288   GVfsBackendSmbBrowse *backend;
289   char *ask_password, *ask_user, *ask_domain;
290   gboolean handled, abort;
291 
292   backend = smbc_getOptionUserData (context);
293 
294   strncpy (password_out, "", pwmaxlen);
295 
296   if (backend->domain)
297     strncpy (domain_out, backend->domain, domainmaxlen);
298   if (backend->user)
299     strncpy (username_out, backend->user, unmaxlen);
300 
301   if (backend->mount_cancelled)
302     {
303       /*  Don't prompt for credentials, let smbclient finish the mount loop  */
304       strncpy (username_out, "ABORT", unmaxlen);
305       strncpy (password_out, "", pwmaxlen);
306       g_debug ("auth_callback - mount_cancelled\n");
307       return;
308     }
309 
310   if (backend->mount_source == NULL)
311     {
312       /* Not during mount, use last password */
313       if (backend->last_user)
314         strncpy (username_out, backend->last_user, unmaxlen);
315       if (backend->last_domain)
316         strncpy (domain_out, backend->last_domain, domainmaxlen);
317       if (backend->last_password)
318         strncpy (password_out, backend->last_password, pwmaxlen);
319       return;
320     }
321 
322   if (backend->mount_try == 0 &&
323       backend->user == NULL &&
324       backend->domain == NULL)
325     {
326       /* Try again if kerberos login + anonymous fallback fails */
327       backend->mount_try_again = TRUE;
328       g_debug ("auth_callback - anonymous pass\n");
329     }
330   else
331     {
332       gboolean in_keyring = FALSE;
333 
334       g_debug ("auth_callback - normal pass\n");
335 
336       if (!backend->password_in_keyring)
337         {
338 	  in_keyring = g_vfs_keyring_lookup_password (backend->user,
339 						      backend->server,
340 						      backend->domain,
341 						      "smb",
342 						      NULL,
343 						      NULL,
344 						      backend->port != -1 ? backend->port : 0,
345 						      &ask_user,
346 						      &ask_domain,
347 						      &ask_password);
348 	  backend->password_in_keyring = in_keyring;
349 
350 	  if (in_keyring)
351             g_debug ("auth_callback - reusing keyring credentials: user = '%s', domain = '%s'\n",
352                      ask_user ? ask_user : "NULL",
353                      ask_domain ? ask_domain : "NULL");
354 	}
355 
356       if (!in_keyring)
357         {
358 	  GAskPasswordFlags flags = G_ASK_PASSWORD_NEED_PASSWORD;
359 	  char *message;
360 
361 	  if (g_vfs_keyring_is_available ())
362 	    flags |= G_ASK_PASSWORD_SAVING_SUPPORTED;
363 	  if (backend->domain == NULL)
364 	    flags |= G_ASK_PASSWORD_NEED_DOMAIN;
365 	  if (backend->user == NULL)
366 	    flags |= G_ASK_PASSWORD_NEED_USERNAME;
367 
368           g_debug ("auth_callback - asking for password...\n");
369 
370 	  /* translators: %s is a server name */
371 	  message = g_strdup_printf (_("Password required for %s"),
372 				     server_name);
373 	  handled = g_mount_source_ask_password (backend->mount_source,
374 						 message,
375 						 username_out,
376 						 domain_out,
377 						 flags,
378 						 &abort,
379 						 &ask_password,
380 						 &ask_user,
381 						 &ask_domain,
382 						 NULL,
383 						 &(backend->password_save));
384 	  g_free (message);
385 	  if (!handled)
386 	    goto out;
387 
388 	  if (abort)
389 	    {
390 	      strncpy (username_out, "ABORT", unmaxlen);
391 	      strncpy (password_out, "", pwmaxlen);
392 	      backend->mount_cancelled = TRUE;
393 	      goto out;
394 	    }
395 	}
396 
397       /* Try again if this fails */
398       backend->mount_try_again = TRUE;
399 
400       strncpy (password_out, ask_password, pwmaxlen);
401       if (ask_user && *ask_user)
402 	strncpy (username_out, ask_user, unmaxlen);
403       if (ask_domain && *ask_domain)
404 	strncpy (domain_out, ask_domain, domainmaxlen);
405 
406     out:
407       g_free (ask_password);
408       g_free (ask_user);
409       g_free (ask_domain);
410     }
411 
412   backend->last_user = g_strdup (username_out);
413   backend->last_domain = g_strdup (domain_out);
414   backend->last_password = g_strdup (password_out);
415   g_debug ("auth_callback - out: last_user = '%s', last_domain = '%s'\n",
416            backend->last_user, backend->last_domain);
417 }
418 
419 /* Add a server to the cache system
420  *
421  * @param c         pointer to smb context
422  * @param srv       pointer to server to add
423  * @param server    server name
424  * @param share     share name
425  * @param workgroup workgroup used to connect
426  * @param username  username used to connect
427  * @return          0 on success. 1 on failure.
428  *
429  */
430 static int
add_cached_server(SMBCCTX * context,SMBCSRV * new,const char * server_name,const char * share_name,const char * domain,const char * username)431 add_cached_server (SMBCCTX *context, SMBCSRV *new,
432 		   const char *server_name, const char *share_name,
433 		   const char *domain, const char *username)
434 {
435   CachedServer *cached_server;
436 
437   cached_server = g_new (CachedServer, 1);
438   cached_server->server_name = g_strdup (server_name);
439   cached_server->share_name = g_strdup (share_name);
440   cached_server->domain = g_strdup (domain);
441   cached_server->username = g_strdup (username);
442 
443   g_debug ("adding cached server '%s'\\'%s', user '%s';'%s' with data %p\n",
444            server_name ? server_name : "NULL",
445            share_name ? share_name : "(no share)",
446            domain ? domain : "(no domain)",
447            username ? username : "NULL",
448            new);
449 
450   if (server_cache == NULL)
451     server_cache = g_hash_table_new_full (cached_server_hash, cached_server_equal,
452 					  (GDestroyNotify)cached_server_free, NULL);
453 
454   g_hash_table_insert (server_cache, cached_server, new);
455 
456   return 0;
457 }
458 
459 static gboolean
remove_cb(gpointer key,gpointer value,gpointer user_data)460 remove_cb (gpointer  key,
461 	   gpointer  value,
462 	   gpointer  user_data)
463 {
464   return value == user_data;
465 }
466 
467 /* Remove cached server
468  *
469  * @param c         pointer to smb context
470  * @param srv       pointer to server to remove
471  * @return          0 when found and removed. 1 on failure.
472  *
473  */
474 static int
remove_cached_server(SMBCCTX * context,SMBCSRV * server)475 remove_cached_server (SMBCCTX * context, SMBCSRV * server)
476 {
477   guint num;
478 
479   if (server_cache)
480     {
481       g_debug ("removing cached servers with data %p\n", server);
482       num = g_hash_table_foreach_remove (server_cache, remove_cb, server);
483       if (num != 0)
484 	return 0;
485     }
486 
487   return 1;
488 }
489 
490 /* Look up a server in the cache system
491  *
492  * @param c         pointer to smb context
493  * @param server    server name to match
494  * @param share     share name to match
495  * @param workgroup workgroup to match
496  * @param username  username to match
497  * @return          pointer to SMBCSRV on success. NULL on failure.
498  *
499  */
500 static SMBCSRV *
get_cached_server(SMBCCTX * context,const char * server_name,const char * share_name,const char * domain,const char * username)501 get_cached_server (SMBCCTX * context,
502 		   const char *server_name, const char *share_name,
503 		   const char *domain, const char *username)
504 {
505   const CachedServer key = {
506     (char *)server_name,
507     (char *)share_name,
508     (char *)domain,
509     (char *)username
510   };
511   SMBCSRV *ret = NULL;
512 
513   g_debug ("looking up cached server '%s'\\'%s', user '%s';'%s'\n",
514            server_name ? server_name : "NULL",
515            share_name ? share_name : "(no share)",
516            domain ? domain : "(no domain)",
517            username ? username : "NULL");
518 
519   if (server_cache)
520     ret = g_hash_table_lookup (server_cache, &key);
521 
522   g_debug ("  returning %p\n", ret);
523   return ret;
524 }
525 
526 /* Try to remove all servers from the cache system and disconnect
527  *
528  * @param c         pointer to smb context
529  *
530  * @return          0 when found and removed. 1 on failure.
531  *
532  */
533 static int
purge_cached(SMBCCTX * context)534 purge_cached (SMBCCTX * context)
535 {
536   g_debug ("purging server cache\n");
537 
538   if (server_cache)
539     g_hash_table_remove_all (server_cache);
540 
541   return 0;
542 }
543 
544 static gboolean
update_cache(GVfsBackendSmbBrowse * backend,SMBCFILE * supplied_dir)545 update_cache (GVfsBackendSmbBrowse *backend, SMBCFILE *supplied_dir)
546 {
547   char *uri;
548   char dirents[1024*4];
549   struct smbc_dirent *dirp;
550   GList *entries;
551   SMBCFILE *dir;
552   int res;
553   smbc_opendir_fn smbc_opendir;
554   smbc_getdents_fn smbc_getdents;
555   smbc_closedir_fn smbc_closedir;
556 
557 
558   entries = NULL;
559   res = -1;
560 
561   g_mutex_lock (&backend->update_cache_lock);
562 
563   g_debug ("update_cache - updating...\n");
564 
565   /* Update Cache */
566 
567   smbc_opendir = smbc_getFunctionOpendir (backend->smb_context);
568   smbc_getdents = smbc_getFunctionGetdents (backend->smb_context);
569   smbc_closedir = smbc_getFunctionClosedir (backend->smb_context);
570 
571   uri = create_smb_uri (backend->server, backend->port, NULL, NULL);
572   dir = supplied_dir ? supplied_dir : smbc_opendir (backend->smb_context, uri);
573   g_free (uri);
574   if (dir == NULL)
575     {
576       goto out;
577     }
578 
579   while (TRUE)
580     {
581       res = smbc_getdents (backend->smb_context, dir, (struct smbc_dirent *)dirents, sizeof (dirents));
582       if (res <= 0)
583         {
584           if (res < 0)
585             g_debug ("update_cache - smbc_getdents returned %d, errno = [%d] %s\n",
586                      res, errno, g_strerror (errno));
587 	  break;
588 	}
589 
590       dirp = (struct smbc_dirent *)dirents;
591       while (res > 0)
592 	{
593 	  unsigned int dirlen;
594 
595 	  if (dirp->smbc_type != SMBC_IPC_SHARE &&
596 	      dirp->smbc_type != SMBC_COMMS_SHARE &&
597 	      dirp->smbc_type != SMBC_PRINTER_SHARE &&
598 	      strcmp (dirp->name, ".") != 0 &&
599 	      strcmp (dirp->name, "..") != 0)
600 	    {
601 	      BrowseEntry *entry = g_new (BrowseEntry, 1);
602 	      gboolean valid_utf8;
603 
604 	      entry->smbc_type = dirp->smbc_type;
605 	      entry->name = g_strdup (dirp->name);
606 	      entry->name_utf8 = smb_name_to_utf8 (dirp->name, &valid_utf8);
607 	      entry->name_normalized = normalize_smb_name_helper (dirp->name, -1, valid_utf8);
608 	      entry->comment = smb_name_to_utf8 (dirp->comment, NULL);
609 
610 	      entries = g_list_prepend (entries, entry);
611 	    }
612 
613 	  dirlen = dirp->dirlen;
614 	  dirp = (struct smbc_dirent *) (((char *)dirp) + dirlen);
615 	  res -= dirlen;
616 	}
617 
618       entries = g_list_reverse (entries);
619     }
620 
621   if (! supplied_dir)
622     smbc_closedir (backend->smb_context, dir);
623 
624 
625  out:
626 
627   g_mutex_lock (&backend->entries_lock);
628 
629   /* Clear old cache */
630   g_list_free_full (backend->entries, (GDestroyNotify)browse_entry_free);
631   backend->entries = entries;
632   backend->last_entry_update = time (NULL);
633 
634   g_debug ("update_cache - done.\n");
635 
636   g_mutex_unlock (&backend->entries_lock);
637   g_mutex_unlock (&backend->update_cache_lock);
638 
639   return (res >= 0);
640 }
641 
642 static BrowseEntry *
find_entry_unlocked(GVfsBackendSmbBrowse * backend,const char * filename)643 find_entry_unlocked (GVfsBackendSmbBrowse *backend,
644 		     const char *filename)
645 {
646   BrowseEntry *entry, *found;
647   GList *l;
648   char *end;
649   int len;
650   char *normalized;
651 
652   while (*filename == '/')
653     filename++;
654 
655   end = strchr (filename, '/');
656   if (end)
657     {
658       len = end - filename;
659 
660       while (*end == '/')
661 	end++;
662 
663       if (*end != 0)
664 	return NULL;
665     }
666   else
667     len = strlen (filename);
668 
669   /* First look for an exact filename match */
670   found = NULL;
671   for (l = backend->entries; l != NULL; l = l->next)
672     {
673       entry = l->data;
674 
675       if (strncmp (filename, entry->name, len) == 0 &&
676 	  strlen (entry->name) == len)
677 	{
678 	  found = entry;
679 	  break;
680 	}
681     }
682 
683   if (found == NULL)
684     {
685       /* That failed, try normalizing the filename */
686       normalized = normalize_smb_name (filename, len);
687 
688       for (l = backend->entries; l != NULL; l = l->next)
689 	{
690 	  entry = l->data;
691 
692 	  if (strcmp (normalized, entry->name_normalized) == 0)
693 	    {
694 	      found = entry;
695 	      break;
696 	    }
697 	}
698       g_free (normalized);
699     }
700 
701   return found;
702 }
703 
704 static GMountSpec *
get_mount_spec_for_share(const char * server,int port,const char * share)705 get_mount_spec_for_share (const char *server,
706 			  int port,
707 			  const char *share)
708 {
709   GMountSpec *mount_spec;
710   char *normalized;
711   char *port_str;
712 
713   mount_spec = g_mount_spec_new ("smb-share");
714   normalized = normalize_smb_name (server, -1);
715   g_mount_spec_set (mount_spec, "server", normalized);
716   g_free (normalized);
717   normalized = normalize_smb_name (share, -1);
718   g_mount_spec_set (mount_spec, "share", normalized);
719   g_free (normalized);
720   if (port != -1)
721     {
722       port_str = g_strdup_printf ("%d", port);
723       g_mount_spec_set (mount_spec, "port", port_str);
724       g_free (port_str);
725     }
726 
727   return mount_spec;
728 }
729 
730 static gboolean
is_root(const char * filename)731 is_root (const char *filename)
732 {
733   const char *p;
734 
735   p = filename;
736   while (*p == '/')
737     p++;
738 
739   return *p == 0;
740 }
741 
742 static gboolean
has_name(GVfsBackendSmbBrowse * backend,const char * filename)743 has_name (GVfsBackendSmbBrowse *backend,
744 	  const char *filename)
745 {
746   gboolean res;
747 
748   g_mutex_lock (&backend->entries_lock);
749   res = (find_entry_unlocked (backend, filename) != NULL);
750   g_mutex_unlock (&backend->entries_lock);
751   return res;
752 }
753 
754 static gboolean
cache_needs_updating(GVfsBackendSmbBrowse * backend)755 cache_needs_updating (GVfsBackendSmbBrowse *backend)
756 {
757   time_t now;
758   gboolean res;
759 
760   /*  If there's already cache update in progress, lock and wait until update is finished, then recheck  */
761   g_mutex_lock (&backend->update_cache_lock);
762   now = time (NULL);
763   res = now < backend->last_entry_update ||
764     (now - backend->last_entry_update) > DEFAULT_CACHE_EXPIRATION_TIME;
765   g_mutex_unlock (&backend->update_cache_lock);
766 
767   return res;
768 }
769 
770 static void
do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)771 do_mount (GVfsBackend *backend,
772 	  GVfsJobMount *job,
773 	  GMountSpec *mount_spec,
774 	  GMountSource *mount_source,
775 	  gboolean is_automount)
776 {
777   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
778   SMBCCTX *smb_context;
779   SMBCFILE *dir;
780   char *display_name;
781   const char *debug;
782   int debug_val;
783   char *icon;
784   char *symbolic_icon;
785   gchar *port_str;
786   char *uri;
787   gboolean res;
788   GMountSpec *browse_mount_spec;
789   smbc_opendir_fn smbc_opendir;
790   smbc_closedir_fn smbc_closedir;
791   int errsv;
792 
793   smb_context = smbc_new_context ();
794   if (smb_context == NULL)
795     {
796       g_vfs_job_failed (G_VFS_JOB (job),
797 			G_IO_ERROR, G_IO_ERROR_FAILED,
798 			_("Internal Error (%s)"), "Failed to allocate smb context");
799       return;
800     }
801 
802   smbc_setOptionUserData (smb_context, backend);
803 
804   debug = g_getenv ("GVFS_SMB_DEBUG");
805   if (debug)
806     debug_val = atoi (debug);
807   else
808     debug_val = 0;
809 
810   smbc_setDebug (smb_context, debug_val);
811   smbc_setFunctionAuthDataWithContext (smb_context, auth_callback);
812 
813   smbc_setFunctionAddCachedServer (smb_context, add_cached_server);
814   smbc_setFunctionGetCachedServer (smb_context, get_cached_server);
815   smbc_setFunctionRemoveCachedServer (smb_context, remove_cached_server);
816   smbc_setFunctionPurgeCachedServers (smb_context, purge_cached);
817 
818   if (op_backend->default_workgroup != NULL)
819     smbc_setWorkgroup (smb_context, op_backend->default_workgroup);
820 
821   /* Initial settings:
822    *   - use Kerberos (always)
823    *   - in case of no username specified, try anonymous login
824    */
825   smbc_setOptionUseKerberos (smb_context, 1);
826   smbc_setOptionFallbackAfterKerberos (smb_context, op_backend->user != NULL);
827   smbc_setOptionNoAutoAnonymousLogin (smb_context, op_backend->user != NULL);
828 
829 #if 0
830   smbc_setOptionDebugToStderr (smb_context, 1);
831 #endif
832 
833   if (!smbc_init_context (smb_context))
834     {
835       g_vfs_job_failed (G_VFS_JOB (job),
836 			G_IO_ERROR, G_IO_ERROR_FAILED,
837 			_("Internal Error (%s)"), "Failed to initialize smb context");
838       smbc_free_context (smb_context, FALSE);
839       return;
840     }
841 
842   op_backend->smb_context = smb_context;
843 
844   /* Convert DEFAULT_WORKGROUP_NAME to real domain */
845   if (op_backend->mounted_server != NULL &&
846       g_ascii_strcasecmp (op_backend->mounted_server, DEFAULT_WORKGROUP_NAME) == 0)
847     op_backend->server = g_strdup (smbc_getWorkgroup (smb_context));
848   else
849     op_backend->server = g_strdup (op_backend->mounted_server);
850 
851 #ifdef HAVE_SMBC_SETOPTIONPROTOCOLS
852   /* Force NT1 protocol version if server can't be resolved (i.e. is not
853    * hostname, nor IP address). This is needed for workgroup support, because
854    * "client max protocol" has been changed from NT1 to SMB3 in recent samba
855    * versions.
856    */
857 
858   if (op_backend->server != NULL)
859     {
860       GResolver *resolver;
861       GList *addresses;
862       GError *error = NULL;
863       gchar *server;
864 
865       resolver = g_resolver_get_default ();
866 
867       /* IPv6 server includes brackets in GMountSpec, GResolver doesn't */
868       if (gvfs_is_ipv6 (op_backend->server))
869         server = g_strndup (op_backend->server + 1, strlen (op_backend->server) - 2);
870       else
871         server = g_strdup (op_backend->server);
872 
873       addresses = g_resolver_lookup_by_name (resolver, server, NULL, &error);
874       if (addresses == NULL)
875         {
876           if (error != NULL)
877             {
878               g_debug ("%s\n", error->message);
879               g_error_free (error);
880             }
881 
882           g_debug ("Forcing NT1 protocol version\n");
883           smbc_setOptionProtocols (smb_context, "NT1", "NT1");
884         }
885 
886       g_resolver_free_addresses (addresses);
887       g_object_unref (resolver);
888       g_free (server);
889     }
890 #endif
891 
892   icon = NULL;
893   symbolic_icon = NULL;
894   if (op_backend->server == NULL)
895     {
896       display_name = g_strdup (_("Windows Network"));
897       browse_mount_spec = g_mount_spec_new ("smb-network");
898       icon = "network-workgroup";
899       symbolic_icon = "network-workgroup-symbolic";
900     }
901   else
902     {
903       /* translators: Name for the location that lists the smb shares
904 	 availible on a server (%s is the name of the server) */
905       display_name = g_strdup_printf (_("Windows shares on %s"), op_backend->server);
906       browse_mount_spec = g_mount_spec_new ("smb-server");
907       g_mount_spec_set (browse_mount_spec, "server", op_backend->mounted_server);
908       if (op_backend->port != -1)
909         {
910           port_str = g_strdup_printf ("%d", op_backend->port);
911           g_mount_spec_set (browse_mount_spec, "port", port_str);
912           g_free (port_str);
913         }
914       icon = "network-server";
915       symbolic_icon = "network-server-symbolic";
916     }
917 
918   if (op_backend->user)
919     g_mount_spec_set (browse_mount_spec, "user", op_backend->user);
920   if (op_backend->domain)
921     g_mount_spec_set (browse_mount_spec, "domain", op_backend->domain);
922 
923   g_vfs_backend_set_display_name (backend, display_name);
924   g_free (display_name);
925   if (icon)
926     g_vfs_backend_set_icon_name (backend, icon);
927   if (symbolic_icon)
928     g_vfs_backend_set_symbolic_icon_name (backend, symbolic_icon);
929   g_vfs_backend_set_user_visible (backend, FALSE);
930   g_vfs_backend_set_mount_spec (backend, browse_mount_spec);
931   g_mount_spec_unref (browse_mount_spec);
932 
933   op_backend->mount_source = mount_source;
934   op_backend->mount_try = 0;
935   op_backend->password_save = G_PASSWORD_SAVE_NEVER;
936 
937   smbc_opendir = smbc_getFunctionOpendir (smb_context);
938   smbc_closedir = smbc_getFunctionClosedir (smb_context);
939 
940   uri = create_smb_uri (op_backend->server, op_backend->port, NULL, NULL);
941 
942   g_debug ("do_mount - URI = %s\n", uri);
943 
944   errsv = 0;
945 
946   do
947     {
948       op_backend->mount_try_again = FALSE;
949       op_backend->mount_cancelled = FALSE;
950 
951       g_debug ("do_mount - try #%d \n", op_backend->mount_try);
952 
953       dir = smbc_opendir (smb_context, uri);
954 
955       errsv = errno;
956       g_debug ("do_mount - [%s; %d] dir = %p, cancelled = %d, errno = [%d] '%s' \n",
957              uri, op_backend->mount_try, dir, op_backend->mount_cancelled,
958              errsv, g_strerror (errsv));
959 
960       if (dir == NULL &&
961           (op_backend->mount_cancelled || (errsv != EPERM && errsv != EACCES)))
962         {
963           g_debug ("do_mount - (errno != EPERM && errno != EACCES), cancelled = %d, breaking\n", op_backend->mount_cancelled);
964 	  break;
965 	}
966 
967       if (dir != NULL)
968         {
969           /*  Let update_cache() do enumeration, check for the smbc_getdents() result */
970           res = update_cache (op_backend, dir);
971           smbc_closedir (smb_context, dir);
972           g_debug ("do_mount - login successful, res = %d\n", res);
973           if (res)
974             break;
975         }
976 	else {
977 	  /*  Purge the cache, we need to have clean playground for next auth try  */
978 	  purge_cached (smb_context);
979 	}
980 
981       /* The first round is Kerberos-only.  Only if this fails do we enable
982        * NTLMSSP fallback (turning off anonymous fallback, which we've
983        * already tried and failed with).
984        */
985       if (op_backend->mount_try == 0)
986         {
987           g_debug ("do_mount - after anon, enabling NTLMSSP fallback\n");
988           smbc_setOptionFallbackAfterKerberos (op_backend->smb_context, 1);
989           smbc_setOptionNoAutoAnonymousLogin (op_backend->smb_context, 1);
990         }
991       op_backend->mount_try++;
992     }
993   while (op_backend->mount_try_again);
994 
995   g_free (uri);
996 
997   op_backend->mount_source = NULL;
998 
999   if (dir == NULL)
1000     {
1001       if (op_backend->mount_cancelled)
1002         g_vfs_job_failed (G_VFS_JOB (job),
1003                          G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
1004                          _("Password dialog cancelled"));
1005       else
1006         g_vfs_job_failed (G_VFS_JOB (job),
1007 			  G_IO_ERROR, G_IO_ERROR_FAILED,
1008 			  /* translators: We tried to mount a windows (samba) share, but failed */
1009 			  _("Failed to retrieve share list from server: %s"), g_strerror (errsv));
1010 
1011       return;
1012     }
1013 
1014   g_vfs_keyring_save_password (op_backend->last_user,
1015 			       op_backend->server,
1016 			       op_backend->last_domain,
1017 			       "smb",
1018 			       NULL,
1019 			       NULL,
1020 			       0,
1021 			       op_backend->last_password,
1022 			       op_backend->password_save);
1023 
1024   g_vfs_job_succeeded (G_VFS_JOB (job));
1025 }
1026 
1027 static gboolean
try_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)1028 try_mount (GVfsBackend *backend,
1029 	   GVfsJobMount *job,
1030 	   GMountSpec *mount_spec,
1031 	   GMountSource *mount_source,
1032 	   gboolean is_automount)
1033 {
1034   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1035   const char *server;
1036   const char *user, *domain;
1037   const char *port;
1038   int port_num;
1039 
1040   if (strcmp (g_mount_spec_get_type (mount_spec), "smb-network") == 0)
1041     server = NULL;
1042   else
1043     {
1044       server = g_mount_spec_get (mount_spec, "server");
1045       if (server == NULL)
1046 	{
1047 	  g_vfs_job_failed (G_VFS_JOB (job),
1048 			    G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1049 			    "No server specified for smb-server share");
1050 	  return TRUE;
1051 	}
1052     }
1053 
1054   user = g_mount_spec_get (mount_spec, "user");
1055   domain = g_mount_spec_get (mount_spec, "domain");
1056   port = g_mount_spec_get (mount_spec, "port");
1057 
1058   if (is_automount &&
1059       ((user != NULL) || (domain != NULL)))
1060     {
1061       g_vfs_job_failed (G_VFS_JOB (job),
1062 			G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1063 			"Can't automount smb browsing with specified user or domain");
1064       return TRUE;
1065     }
1066 
1067   op_backend->user = g_strdup (user);
1068   op_backend->domain = g_strdup (domain);
1069   op_backend->mounted_server = g_strdup (server);
1070   if (port && (port_num = atoi (port)))
1071       op_backend->port = port_num;
1072   else
1073       op_backend->port = -1;
1074 
1075   return FALSE;
1076 }
1077 
1078 static void
run_mount_mountable(GVfsBackendSmbBrowse * backend,GVfsJobMountMountable * job,const char * filename,GMountSource * mount_source)1079 run_mount_mountable (GVfsBackendSmbBrowse *backend,
1080 		     GVfsJobMountMountable *job,
1081 		     const char *filename,
1082 		     GMountSource *mount_source)
1083 {
1084   BrowseEntry *entry;
1085   GError *error = NULL;
1086   GMountSpec *mount_spec;
1087 
1088   g_mutex_lock (&backend->entries_lock);
1089 
1090   entry = find_entry_unlocked (backend, filename);
1091 
1092   if (entry)
1093     {
1094       if (backend->server != NULL &&
1095 	  entry->smbc_type == SMBC_FILE_SHARE)
1096 	{
1097 	  mount_spec = get_mount_spec_for_share (backend->server, backend->port, entry->name);
1098 	  g_vfs_job_mount_mountable_set_target (job, mount_spec, "/", TRUE);
1099 	  g_mount_spec_unref (mount_spec);
1100 	}
1101       else
1102 	g_set_error_literal (&error,
1103 			     G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE,
1104 			     _("Not a mountable file"));
1105     }
1106   else
1107     g_set_error_literal (&error,
1108 			 G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1109 			 _("File doesn’t exist"));
1110 
1111   g_mutex_unlock (&backend->entries_lock);
1112 
1113   if (error)
1114     {
1115       g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
1116       g_error_free (error);
1117     }
1118   else
1119     g_vfs_job_succeeded (G_VFS_JOB (job));
1120 }
1121 
1122 static void
do_mount_mountable(GVfsBackend * backend,GVfsJobMountMountable * job,const char * filename,GMountSource * mount_source)1123 do_mount_mountable (GVfsBackend *backend,
1124 		    GVfsJobMountMountable *job,
1125 		    const char *filename,
1126 		    GMountSource *mount_source)
1127 {
1128   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1129 
1130   update_cache (op_backend, NULL);
1131 
1132   run_mount_mountable (op_backend,
1133 		       job,
1134 		       filename,
1135 		       mount_source);
1136 }
1137 
1138 static gboolean
try_mount_mountable(GVfsBackend * backend,GVfsJobMountMountable * job,const char * filename,GMountSource * mount_source)1139 try_mount_mountable (GVfsBackend *backend,
1140 		     GVfsJobMountMountable *job,
1141 		     const char *filename,
1142 		     GMountSource *mount_source)
1143 {
1144   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1145 
1146   if (is_root (filename))
1147     {
1148       g_vfs_job_failed (G_VFS_JOB (job),
1149 			G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE,
1150 			_("Not a mountable file"));
1151       return TRUE;
1152     }
1153 
1154   if (cache_needs_updating (op_backend))
1155     return FALSE;
1156 
1157   run_mount_mountable (op_backend,
1158 		       job,
1159 		       filename,
1160 		       mount_source);
1161   return TRUE;
1162 }
1163 
1164 static void
run_open_for_read(GVfsBackendSmbBrowse * backend,GVfsJobOpenForRead * job,const char * filename)1165 run_open_for_read (GVfsBackendSmbBrowse *backend,
1166 		    GVfsJobOpenForRead *job,
1167 		    const char *filename)
1168 {
1169   if (has_name (backend, filename))
1170     g_vfs_job_failed (G_VFS_JOB (job),
1171 		      G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
1172 		      _("Not a regular file"));
1173   else
1174     g_vfs_job_failed (G_VFS_JOB (job),
1175 		      G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1176 		      _("File doesn’t exist"));
1177 }
1178 
1179 static void
do_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)1180 do_open_for_read (GVfsBackend *backend,
1181 	       GVfsJobOpenForRead *job,
1182 	       const char *filename)
1183 {
1184   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1185 
1186   update_cache (op_backend, NULL);
1187 
1188   run_open_for_read (op_backend, job, filename);
1189 }
1190 
1191 static gboolean
try_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)1192 try_open_for_read (GVfsBackend *backend,
1193 		   GVfsJobOpenForRead *job,
1194 		   const char *filename)
1195 {
1196   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1197 
1198   if (cache_needs_updating (op_backend))
1199     return FALSE;
1200 
1201   run_open_for_read (op_backend, job, filename);
1202 
1203   return TRUE;
1204 }
1205 
1206 static gboolean
try_read(GVfsBackend * backend,GVfsJobRead * job,GVfsBackendHandle handle,char * buffer,gsize bytes_requested)1207 try_read (GVfsBackend *backend,
1208 	  GVfsJobRead *job,
1209 	  GVfsBackendHandle handle,
1210 	  char *buffer,
1211 	  gsize bytes_requested)
1212 {
1213   g_vfs_job_failed (G_VFS_JOB (job),
1214 		    G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1215 		    "Invalid argument");
1216 
1217   return TRUE;
1218 }
1219 
1220 static gboolean
try_seek_on_read(GVfsBackend * backend,GVfsJobSeekRead * job,GVfsBackendHandle handle,goffset offset,GSeekType type)1221 try_seek_on_read (GVfsBackend *backend,
1222 		 GVfsJobSeekRead *job,
1223 		 GVfsBackendHandle handle,
1224 		 goffset    offset,
1225 		 GSeekType  type)
1226 {
1227   g_vfs_job_failed (G_VFS_JOB (job),
1228 		    G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1229 		    "Invalid argument");
1230   return TRUE;
1231 }
1232 
1233 static gboolean
try_close_read(GVfsBackend * backend,GVfsJobCloseRead * job,GVfsBackendHandle handle)1234 try_close_read (GVfsBackend *backend,
1235 	       GVfsJobCloseRead *job,
1236 	       GVfsBackendHandle handle)
1237 {
1238   g_vfs_job_failed (G_VFS_JOB (job),
1239 		    G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
1240 		    "Invalid argument");
1241   return TRUE;
1242 }
1243 
1244 static void
get_file_info_from_entry(GVfsBackendSmbBrowse * backend,BrowseEntry * entry,GFileInfo * info)1245 get_file_info_from_entry (GVfsBackendSmbBrowse *backend, BrowseEntry *entry, GFileInfo *info)
1246 {
1247   GMountSpec *mount_spec;
1248   GString *uri;
1249   GIcon *icon;
1250   GIcon *symbolic_icon;
1251 
1252   g_file_info_set_name (info, entry->name);
1253   g_file_info_set_display_name (info, entry->name_utf8);
1254   g_file_info_set_edit_name (info, entry->name_utf8);
1255   g_file_info_set_attribute_string (info, "smb::comment", entry->comment);
1256   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, TRUE);
1257   g_file_info_set_content_type (info, "inode/directory");
1258 
1259   icon = NULL;
1260   if (entry->smbc_type == SMBC_WORKGROUP)
1261     {
1262       icon = g_themed_icon_new ("network-workgroup");
1263       symbolic_icon = g_themed_icon_new ("network-workgroup-symbolic");
1264     }
1265   else if (entry->smbc_type == SMBC_SERVER)
1266     {
1267       icon = g_themed_icon_new ("network-server");
1268       symbolic_icon = g_themed_icon_new ("network-server-symbolic");
1269     }
1270   else
1271     {
1272       icon = g_themed_icon_new ("folder-remote");
1273       symbolic_icon = g_themed_icon_new ("folder-remote-symbolic");
1274     }
1275 
1276   if (icon)
1277     {
1278       g_file_info_set_icon (info, icon);
1279       g_object_unref (icon);
1280     }
1281   if (symbolic_icon)
1282     {
1283       g_file_info_set_symbolic_icon (info, symbolic_icon);
1284       g_object_unref (symbolic_icon);
1285     }
1286 
1287   mount_spec = NULL;
1288   if (backend->server)
1289     {
1290       /* browsing server/workgroup */
1291       if (entry->smbc_type == SMBC_WORKGROUP ||
1292 	  entry->smbc_type == SMBC_SERVER)
1293 	{
1294 	  uri = g_string_new ("smb://");
1295           g_string_append_uri_escaped (uri, entry->name, NULL, FALSE);
1296 	  g_string_append_c (uri, '/');
1297 	}
1298       else
1299 	{
1300 	  mount_spec = get_mount_spec_for_share (backend->server, backend->port, entry->name);
1301 
1302 	  uri = g_string_new ("smb://");
1303           g_string_append_uri_escaped (uri, backend->server, NULL, FALSE);
1304 	  g_string_append_c (uri, '/');
1305           g_string_append_uri_escaped (uri, entry->name, NULL, FALSE);
1306 	}
1307     }
1308   else
1309     {
1310       /* browsing network */
1311       uri = g_string_new ("smb://");
1312       g_string_append_uri_escaped (uri, entry->name, NULL, FALSE);
1313       g_string_append_c (uri, '/');
1314 
1315       /* these are auto-mounted, so no CAN_MOUNT/UNMOUNT */
1316     }
1317 
1318   if (mount_spec)
1319     {
1320       g_file_info_set_file_type (info, G_FILE_TYPE_MOUNTABLE);
1321       if (g_mount_tracker_has_mount_spec (mount_tracker, mount_spec))
1322         g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, FALSE);
1323       else
1324         g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, TRUE);
1325       g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT, FALSE);
1326       g_mount_spec_unref (mount_spec);
1327     }
1328   else
1329     g_file_info_set_file_type (info, G_FILE_TYPE_SHORTCUT);
1330 
1331   g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, uri->str);
1332   g_string_free (uri, TRUE);
1333 }
1334 
1335 static void
run_query_info(GVfsBackendSmbBrowse * backend,GVfsJobQueryInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * matcher)1336 run_query_info (GVfsBackendSmbBrowse *backend,
1337 		GVfsJobQueryInfo *job,
1338 		const char *filename,
1339 		GFileInfo *info,
1340 		GFileAttributeMatcher *matcher)
1341 {
1342   BrowseEntry *entry;
1343 
1344   g_mutex_lock (&backend->entries_lock);
1345 
1346   entry = find_entry_unlocked (backend, filename);
1347 
1348   if (entry)
1349     get_file_info_from_entry (backend, entry, info);
1350 
1351   g_mutex_unlock (&backend->entries_lock);
1352 
1353   if (entry)
1354     g_vfs_job_succeeded (G_VFS_JOB (job));
1355   else
1356     g_vfs_job_failed (G_VFS_JOB (job),
1357 		      G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1358 		      _("File doesn’t exist"));
1359 }
1360 
1361 static void
do_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * matcher)1362 do_query_info (GVfsBackend *backend,
1363 	       GVfsJobQueryInfo *job,
1364 	       const char *filename,
1365 	       GFileQueryInfoFlags flags,
1366 	       GFileInfo *info,
1367 	       GFileAttributeMatcher *matcher)
1368 {
1369   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1370 
1371   update_cache (op_backend, NULL);
1372 
1373   run_query_info (op_backend, job, filename, info, matcher);
1374 }
1375 
1376 
1377 static gboolean
try_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * matcher)1378 try_query_info (GVfsBackend *backend,
1379 		GVfsJobQueryInfo *job,
1380 		const char *filename,
1381 		GFileQueryInfoFlags flags,
1382 		GFileInfo *info,
1383 		GFileAttributeMatcher *matcher)
1384 {
1385   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1386   GIcon *icon;
1387 
1388   if (is_root (filename))
1389     {
1390       g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
1391       g_file_info_set_name (info, "/");
1392       g_file_info_set_display_name (info, g_vfs_backend_get_display_name (backend));
1393       g_file_info_set_content_type (info, "inode/directory");
1394       icon = g_vfs_backend_get_icon (backend);
1395       if (icon != NULL)
1396         g_file_info_set_icon (info, icon);
1397       icon = g_vfs_backend_get_symbolic_icon (backend);
1398       if (icon != NULL)
1399         g_file_info_set_symbolic_icon (info, icon);
1400       g_vfs_job_succeeded (G_VFS_JOB (job));
1401 
1402       return TRUE;
1403     }
1404 
1405   if (cache_needs_updating (op_backend))
1406     return FALSE;
1407 
1408   run_query_info (op_backend, job, filename, info, matcher);
1409 
1410   return TRUE;
1411 }
1412 
1413 static void
run_enumerate(GVfsBackendSmbBrowse * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * matcher)1414 run_enumerate (GVfsBackendSmbBrowse *backend,
1415 	       GVfsJobEnumerate *job,
1416 	       const char *filename,
1417 	       GFileAttributeMatcher *matcher)
1418 {
1419   GList *files, *l;
1420   GFileInfo *info;
1421 
1422   if (!is_root (filename))
1423     {
1424       if (has_name (backend, filename))
1425 	g_vfs_job_failed (G_VFS_JOB (job),
1426 			  G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
1427 			  _("Not a directory"));
1428       else
1429 	g_vfs_job_failed (G_VFS_JOB (job),
1430 			  G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
1431 			  _("File doesn’t exist"));
1432       return;
1433     }
1434 
1435   /* TODO: limit requested to what we support */
1436   g_vfs_job_succeeded (G_VFS_JOB (job));
1437 
1438   files = NULL;
1439   g_mutex_lock (&backend->entries_lock);
1440   for (l = backend->entries; l != NULL; l = l->next)
1441     {
1442       BrowseEntry *entry = l->data;
1443 
1444       info = g_file_info_new ();
1445       get_file_info_from_entry (backend, entry, info);
1446 
1447       files = g_list_prepend (files, info);
1448     }
1449   g_mutex_unlock (&backend->entries_lock);
1450 
1451   files = g_list_reverse (files);
1452 
1453   g_vfs_job_enumerate_add_infos (job, files);
1454   g_list_free_full (files, g_object_unref);
1455 
1456   g_vfs_job_enumerate_done (job);
1457 }
1458 
1459 static void
do_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * matcher,GFileQueryInfoFlags flags)1460 do_enumerate (GVfsBackend *backend,
1461 	      GVfsJobEnumerate *job,
1462 	      const char *filename,
1463 	      GFileAttributeMatcher *matcher,
1464 	      GFileQueryInfoFlags flags)
1465 {
1466   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1467 
1468   update_cache (op_backend, NULL);
1469 
1470   run_enumerate (op_backend, job, filename, matcher);
1471 }
1472 
1473 static gboolean
try_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * matcher,GFileQueryInfoFlags flags)1474 try_enumerate (GVfsBackend *backend,
1475 	       GVfsJobEnumerate *job,
1476 	       const char *filename,
1477 	       GFileAttributeMatcher *matcher,
1478 	       GFileQueryInfoFlags flags)
1479 {
1480   GVfsBackendSmbBrowse *op_backend = G_VFS_BACKEND_SMB_BROWSE (backend);
1481 
1482   if (cache_needs_updating (op_backend))
1483     return FALSE;
1484 
1485   run_enumerate (op_backend, job, filename, matcher);
1486 
1487   return TRUE;
1488 }
1489 
1490 static gboolean
try_query_fs_info(GVfsBackend * backend,GVfsJobQueryFsInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * matcher)1491 try_query_fs_info (GVfsBackend *backend,
1492                    GVfsJobQueryFsInfo *job,
1493                    const char *filename,
1494                    GFileInfo *info,
1495                    GFileAttributeMatcher *matcher)
1496 {
1497   g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "cifs");
1498   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE);
1499   g_vfs_job_succeeded (G_VFS_JOB (job));
1500   return TRUE;
1501 }
1502 
1503 static void
g_vfs_backend_smb_browse_class_init(GVfsBackendSmbBrowseClass * klass)1504 g_vfs_backend_smb_browse_class_init (GVfsBackendSmbBrowseClass *klass)
1505 {
1506   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1507   GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
1508 
1509   gobject_class->finalize = g_vfs_backend_smb_browse_finalize;
1510 
1511   backend_class->mount = do_mount;
1512   backend_class->try_mount = try_mount;
1513   backend_class->mount_mountable = do_mount_mountable;
1514   backend_class->try_mount_mountable = try_mount_mountable;
1515   backend_class->open_for_read = do_open_for_read;
1516   backend_class->try_open_for_read = try_open_for_read;
1517   backend_class->try_read = try_read;
1518   backend_class->try_seek_on_read = try_seek_on_read;
1519   backend_class->try_close_read = try_close_read;
1520   backend_class->query_info = do_query_info;
1521   backend_class->try_query_info = try_query_info;
1522   backend_class->try_query_fs_info = try_query_fs_info;
1523   backend_class->enumerate = do_enumerate;
1524   backend_class->try_enumerate = try_enumerate;
1525 }
1526 
1527 void
g_vfs_smb_browse_daemon_init(void)1528 g_vfs_smb_browse_daemon_init (void)
1529 {
1530   g_set_application_name (_("Windows Network File System Service"));
1531 }
1532