1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 
5 #include <glib/gi18n-lib.h>
6 
7 #include <string.h>
8 #include <time.h>
9 #include <sys/types.h>
10 #include <sys/wait.h>
11 
12 #include "shares.h"
13 
14 #undef DEBUG_SHARES
15 #ifdef DEBUG_SHARES
16 #  define NET_USERSHARE_ARGV0 "debug-net-usershare"
17 #else
18 #  define NET_USERSHARE_ARGV0 "net"
19 #endif
20 
21 static GHashTable *path_share_info_hash;
22 static GHashTable *share_name_share_info_hash;
23 
24 #define NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES 100
25 #define TIMESTAMP_THRESHOLD 10	/* seconds */
26 static int refresh_timestamp_update_counter;
27 static time_t refresh_timestamp;
28 
29 #define KEY_PATH "path"
30 #define KEY_COMMENT "comment"
31 #define KEY_ACL "usershare_acl"
32 #define KEY_GUEST_OK "guest_ok"
33 
34 
35 /* Debugging flags */
36 static gboolean throw_error_on_refresh;
37 static gboolean throw_error_on_add;
38 static gboolean throw_error_on_modify;
39 static gboolean throw_error_on_remove;
40 
41 /* Interface to "net usershare" */
42 
43 static gboolean
net_usershare_run(int argc,char ** argv,GKeyFile ** ret_key_file,GError ** error)44 net_usershare_run (int argc, char **argv, GKeyFile **ret_key_file, GError **error)
45 {
46 	int real_argc;
47 	int i;
48 	char **real_argv;
49 	gboolean retval;
50 	char *stdout_contents;
51 	char *stderr_contents;
52 	int exit_status;
53 	int exit_code;
54 	GKeyFile *key_file;
55 	GError *real_error;
56 
57 	g_assert (argc > 0);
58 	g_assert (argv != NULL);
59 	g_assert (error == NULL || *error == NULL);
60 
61 	if (ret_key_file)
62 		*ret_key_file = NULL;
63 
64 	/* Build command line */
65 
66 	real_argc = 2 + argc + 1; /* "net" "usershare" [argv] NULL */
67 	real_argv = g_new (char *, real_argc);
68 
69 	real_argv[0] = NET_USERSHARE_ARGV0;
70 	real_argv[1] = "usershare";
71 
72 	for (i = 0; i < argc; i++) {
73 		g_assert (argv[i] != NULL);
74 		real_argv[i + 2] = argv[i];
75 	}
76 
77 	real_argv[real_argc - 1] = NULL;
78 
79 	/* Launch */
80 
81 	stdout_contents = NULL;
82 	stderr_contents = NULL;
83 	/*
84 	{
85 		char **p;
86 
87 		g_message ("------------------------------------------");
88 
89 		for (p = real_argv; *p; p++)
90 			g_message ("spawn arg \"%s\"", *p);
91 
92 		g_message ("end of spawn args; SPAWNING\n");
93 	}
94 	*/
95 	real_error = NULL;
96 	retval = g_spawn_sync (NULL,			/* cwd */
97 			       real_argv,
98 			       NULL, 			/* envp */
99 			       G_SPAWN_SEARCH_PATH,
100 			       NULL, 			/* GSpawnChildSetupFunc */
101 			       NULL,			/* user_data */
102 			       &stdout_contents,
103 			       &stderr_contents,
104 			       &exit_status,
105 			       &real_error);
106 
107 	/* g_message ("returned from spawn: %s: %s", retval ? "SUCCESS" : "FAIL", retval ? "" : real_error->message); */
108 
109 	if (!retval) {
110 		g_propagate_error (error, real_error);
111 		goto out;
112 	}
113 
114 	if (!WIFEXITED (exit_status)) {
115 		g_message ("WIFEXITED(%d) was false!", exit_status);
116 		retval = FALSE;
117 
118 		if (WIFSIGNALED (exit_status)) {
119 			int signal_num;
120 
121 			signal_num = WTERMSIG (exit_status);
122 			g_message ("Child got signal %d", signal_num);
123 
124 			g_set_error (error,
125 				     SHARES_ERROR,
126 				     SHARES_ERROR_FAILED,
127 				     _("%s %s %s returned with signal %d"),
128 				     real_argv[0],
129 				     real_argv[1],
130 				     real_argv[2],
131 				     signal_num);
132 		} else
133 			g_set_error (error,
134 				     SHARES_ERROR,
135 				     SHARES_ERROR_FAILED,
136 				     _("%s %s %s failed for an unknown reason"),
137 				     real_argv[0],
138 				     real_argv[1],
139 				     real_argv[2]);
140 
141 		goto out;
142 	}
143 
144 	exit_code = WEXITSTATUS (exit_status);
145 
146 	/* g_message ("exit code %d", exit_code); */
147 	if (exit_code != 0) {
148 		char *str;
149 		char *message;
150 
151 		/* stderr_contents is in the system locale encoding, not UTF-8 */
152 
153 		str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL);
154 
155 		if (str && str[0])
156 			message = g_strdup_printf (_("'net usershare' returned error %d: %s"), exit_code, str);
157 		else
158 			message = g_strdup_printf (_("'net usershare' returned error %d"), exit_code);
159 
160 		g_free (str);
161 
162 		g_set_error (error,
163 			     G_SPAWN_ERROR,
164 			     G_SPAWN_ERROR_FAILED,
165 			     "%s",
166 			     message);
167 
168 		g_free (message);
169 
170 		retval = FALSE;
171 		goto out;
172 	}
173 
174 	if (ret_key_file) {
175 	  /* g_message ("caller wants GKeyFile"); */
176 
177 		*ret_key_file = NULL;
178 
179 		/* FIXME: jeallison@novell.com says the output of "net usershare" is nearly always
180 		 * in UTF-8, but that it can be configured in the master smb.conf.  We assume
181 		 * UTF-8 for now.
182 		 */
183 
184 		if (!g_utf8_validate (stdout_contents, -1, NULL)) {
185 			g_message ("stdout of net usershare was not in valid UTF-8");
186 			g_set_error (error,
187 				     G_SPAWN_ERROR,
188 				     G_SPAWN_ERROR_FAILED,
189 				     _("the output of 'net usershare' is not in valid UTF-8 encoding"));
190 			retval = FALSE;
191 			goto out;
192 		}
193 
194 		key_file = g_key_file_new ();
195 
196 		real_error = NULL;
197 		if (!g_key_file_load_from_data (key_file, stdout_contents, -1, 0, &real_error)) {
198 			g_message ("Error when parsing key file {\n%s\n}: %s", stdout_contents, real_error->message);
199 			g_propagate_error (error, real_error);
200 			g_key_file_free (key_file);
201 			retval = FALSE;
202 			goto out;
203 		}
204 
205 		retval = TRUE;
206 		*ret_key_file = key_file;
207 	} else
208 		retval = TRUE;
209 
210 	/* g_message ("success from calling net usershare and parsing its output"); */
211 
212  out:
213 	g_free (real_argv);
214 	g_free (stdout_contents);
215 	g_free (stderr_contents);
216 
217 	/* g_message ("------------------------------------------"); */
218 
219 	return retval;
220 }
221 
222 
223 /* Internals */
224 
225 static void
ensure_hashes(void)226 ensure_hashes (void)
227 {
228 	if (path_share_info_hash == NULL) {
229 		g_assert (share_name_share_info_hash == NULL);
230 
231 		path_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal);
232 		share_name_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal);
233 	} else
234 		g_assert (share_name_share_info_hash != NULL);
235 }
236 
237 static ShareInfo *
lookup_share_by_path(const char * path)238 lookup_share_by_path (const char *path)
239 {
240 	ensure_hashes ();
241 	return g_hash_table_lookup (path_share_info_hash, path);
242 }
243 
244 static ShareInfo *
lookup_share_by_share_name(const char * share_name)245 lookup_share_by_share_name (const char *share_name)
246 {
247 	ensure_hashes ();
248 	return g_hash_table_lookup (share_name_share_info_hash, share_name);
249 }
250 
251 static void
add_share_info_to_hashes(ShareInfo * info)252 add_share_info_to_hashes (ShareInfo *info)
253 {
254 	ensure_hashes ();
255 	g_hash_table_insert (path_share_info_hash, info->path, info);
256 	g_hash_table_insert (share_name_share_info_hash, info->share_name, info);
257 }
258 
259 static void
remove_share_info_from_hashes(ShareInfo * info)260 remove_share_info_from_hashes (ShareInfo *info)
261 {
262 	ensure_hashes ();
263 	g_hash_table_remove (path_share_info_hash, info->path);
264 	g_hash_table_remove (share_name_share_info_hash, info->share_name);
265 }
266 
267 static gboolean
remove_from_path_hash_cb(gpointer key,gpointer value,gpointer data)268 remove_from_path_hash_cb (gpointer key,
269 			  gpointer value,
270 			  gpointer data)
271 {
272 	ShareInfo *info;
273 
274 	info = value;
275 	shares_free_share_info (info);
276 
277 	return TRUE;
278 }
279 
280 static gboolean
remove_from_share_name_hash_cb(gpointer key,gpointer value,gpointer data)281 remove_from_share_name_hash_cb (gpointer key,
282 				gpointer value,
283 				gpointer data)
284 {
285 	/* The ShareInfo was already freed in remove_from_path_hash_cb() */
286 	return TRUE;
287 }
288 
289 static void
free_all_shares(void)290 free_all_shares (void)
291 {
292 	ensure_hashes ();
293 	g_hash_table_foreach_remove (path_share_info_hash, remove_from_path_hash_cb, NULL);
294 	g_hash_table_foreach_remove (share_name_share_info_hash, remove_from_share_name_hash_cb, NULL);
295 }
296 
297 static char *
get_string_from_key_file(GKeyFile * key_file,const char * group,const char * key)298 get_string_from_key_file (GKeyFile *key_file, const char *group, const char *key)
299 {
300 	GError *error;
301 	char *str;
302 
303 	error = NULL;
304 	str = NULL;
305 
306 	if (g_key_file_has_key (key_file, group, key, &error)) {
307 		str = g_key_file_get_string (key_file, group, key, &error);
308 		if (!str) {
309 			g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)
310 				  && !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND));
311 
312 			g_error_free (error);
313 		}
314 	} else {
315 		g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND));
316 		g_error_free (error);
317 	}
318 
319 	return str;
320 }
321 
322 static void
add_key_group_to_hashes(GKeyFile * key_file,const char * group)323 add_key_group_to_hashes (GKeyFile *key_file, const char *group)
324 {
325 	char *path;
326 	char *comment;
327 	char *acl;
328 	gboolean is_writable;
329 	char *guest_ok_str;
330 	gboolean guest_ok;
331 	ShareInfo *info;
332 	ShareInfo *old_info;
333 
334 	/* Remove the old share based on the name */
335 
336 	old_info = lookup_share_by_share_name (group);
337 	if (old_info) {
338 		remove_share_info_from_hashes (old_info);
339 		shares_free_share_info (old_info);
340 	}
341 
342 	/* Start parsing, and remove the old share based on the path */
343 
344 	path = get_string_from_key_file (key_file, group, KEY_PATH);
345 	if (!path) {
346 		g_message ("group '%s' doesn't have a '%s' key!  Ignoring group.", group, KEY_PATH);
347 		return;
348 	}
349 
350 	old_info = lookup_share_by_path (path);
351 	if (old_info) {
352 		remove_share_info_from_hashes (old_info);
353 		shares_free_share_info (old_info);
354 	}
355 
356 	/* Finish parsing */
357 
358 	comment = get_string_from_key_file (key_file, group, KEY_COMMENT);
359 
360 	acl = get_string_from_key_file (key_file, group, KEY_ACL);
361 	if (acl) {
362 		if (strstr (acl, "Everyone:R"))
363 			is_writable = FALSE;
364 		else if (strstr (acl, "Everyone:F"))
365 			is_writable = TRUE;
366 		else {
367 			g_message ("unknown format for key '%s/%s' as it contains '%s'.  Assuming that the share is read-only",
368 				   group, KEY_ACL, acl);
369 			is_writable = FALSE;
370 		}
371 
372 		g_free (acl);
373 	} else {
374 		g_message ("group '%s' doesn't have a '%s' key!  Assuming that the share is read-only.", group, KEY_ACL);
375 		is_writable = FALSE;
376 	}
377 
378 	guest_ok_str = get_string_from_key_file (key_file, group, KEY_GUEST_OK);
379 	if (guest_ok_str) {
380 		if (strcmp (guest_ok_str, "n") == 0)
381 			guest_ok = FALSE;
382 		else if (strcmp (guest_ok_str, "y") == 0)
383 			guest_ok = TRUE;
384 		else {
385 			g_message ("unknown format for key '%s/%s' as it contains '%s'.  Assuming that the share is not guest accessible.",
386 				   group, KEY_GUEST_OK, guest_ok_str);
387 			guest_ok = FALSE;
388 		}
389 
390 		g_free (guest_ok_str);
391 	} else {
392 		g_message ("group '%s' doesn't have a '%s' key!  Assuming that the share is not guest accessible.", group, KEY_GUEST_OK);
393 		guest_ok = FALSE;
394 	}
395 
396 	g_assert (path != NULL);
397 	g_assert (group != NULL);
398 
399 	info = g_new (ShareInfo, 1);
400 	info->path = path;
401 	info->share_name = g_strdup (group);
402 	info->comment = comment;
403 	info->is_writable = is_writable;
404 	info->guest_ok = guest_ok;
405 
406 	add_share_info_to_hashes (info);
407 }
408 
409 static void
replace_shares_from_key_file(GKeyFile * key_file)410 replace_shares_from_key_file (GKeyFile *key_file)
411 {
412 	gsize num_groups;
413 	char **group_names;
414 	gsize i;
415 
416 	group_names = g_key_file_get_groups (key_file, &num_groups);
417 
418 	/* FIXME: In add_key_group_to_hashes(), we simply ignore key groups
419 	 * which have invalid data (i.e. no path).  We could probably accumulate a
420 	 * GError with the list of invalid groups and propagate it upwards.
421 	 */
422 	for (i = 0; i < num_groups; i++) {
423 		g_assert (group_names[i] != NULL);
424 		add_key_group_to_hashes (key_file, group_names[i]);
425 	}
426 
427 	g_strfreev (group_names);
428 }
429 
430 static gboolean
refresh_shares(GError ** error)431 refresh_shares (GError **error)
432 {
433 	GKeyFile *key_file;
434 	char *argv[1];
435 	GError *real_error;
436 
437 	free_all_shares ();
438 
439 	if (throw_error_on_refresh) {
440 		g_set_error (error,
441 			     SHARES_ERROR,
442 			     SHARES_ERROR_FAILED,
443 			     _("Failed"));
444 		return FALSE;
445 	}
446 
447 	argv[0] = "info";
448 
449 	real_error = NULL;
450 	if (!net_usershare_run (G_N_ELEMENTS (argv), argv, &key_file, &real_error)) {
451 		g_message ("Called \"net usershare info\" but it failed: %s", real_error->message);
452 		g_propagate_error (error, real_error);
453 		return FALSE;
454 	}
455 
456 	g_assert (key_file != NULL);
457 
458 	replace_shares_from_key_file (key_file);
459 	g_key_file_free (key_file);
460 
461 	return TRUE;
462 }
463 
464 static gboolean
refresh_if_needed(GError ** error)465 refresh_if_needed (GError **error)
466 {
467 	gboolean retval;
468 
469 	if (refresh_timestamp_update_counter == 0) {
470 		time_t new_timestamp;
471 
472 		refresh_timestamp_update_counter = NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES;
473 
474 		new_timestamp = time (NULL);
475 		if (new_timestamp - refresh_timestamp > TIMESTAMP_THRESHOLD) {
476 		  /* g_message ("REFRESHING SHARES"); */
477 			retval = refresh_shares (error);
478 		} else
479 			retval = TRUE;
480 
481 		refresh_timestamp = new_timestamp;
482 	} else {
483 		refresh_timestamp_update_counter--;
484 		retval = TRUE;
485 	}
486 
487 	return retval;
488 }
489 
490 static ShareInfo *
copy_share_info(ShareInfo * info)491 copy_share_info (ShareInfo *info)
492 {
493 	ShareInfo *copy;
494 
495 	if (!info)
496 		return NULL;
497 
498 	copy = g_new (ShareInfo, 1);
499 	copy->path = g_strdup (info->path);
500 	copy->share_name = g_strdup (info->share_name);
501 	copy->comment = g_strdup (info->comment);
502 	copy->is_writable = info->is_writable;
503 	copy->guest_ok = info->guest_ok;
504 
505 	return copy;
506 }
507 
508 /**
509  * shares_supports_guest_ok:
510  * @supports_guest_ok_ret: Location to store whether "usershare allow guests"
511  * is enabled.
512  * @error: Location to store error, or #NULL.
513  *
514  * Determines whether the option "usershare allow guests" is enabled in samba
515  * config as shown by testparm.
516  *
517  * Return value: #TRUE if if the info could be queried successfully, #FALSE
518  * otherwise.  If this function returns #FALSE, an error code will be returned
519  * in the @error argument, and *@ret_info_list will be set to #FALSE.
520  **/
521 gboolean
shares_supports_guest_ok(gboolean * supports_guest_ok_ret,GError ** error)522 shares_supports_guest_ok (gboolean *supports_guest_ok_ret, GError **error)
523 {
524 	gboolean retval;
525 	gboolean result;
526 	char *stdout_contents;
527 	char *stderr_contents;
528 	int exit_status;
529 	int exit_code;
530 
531 	*supports_guest_ok_ret = FALSE;
532 
533 	result = g_spawn_command_line_sync ("testparm -s --parameter-name='usershare allow guests'",
534 					    &stdout_contents,
535 					    &stderr_contents,
536 					    &exit_status,
537 					    error);
538 	if (!result)
539 		return FALSE;
540 
541 	retval = FALSE;
542 
543 	if (!WIFEXITED (exit_status)) {
544 		if (WIFSIGNALED (exit_status)) {
545 			int signal_num;
546 
547 			signal_num = WTERMSIG (exit_status);
548 			g_set_error (error,
549 				     SHARES_ERROR,
550 				     SHARES_ERROR_FAILED,
551 				     _("Samba's testparm returned with signal %d"),
552 				     signal_num);
553 		} else
554 			g_set_error (error,
555 				     SHARES_ERROR,
556 				     SHARES_ERROR_FAILED,
557 				     _("Samba's testparm failed for an unknown reason"));
558 
559 		goto out;
560 	}
561 
562 	exit_code = WEXITSTATUS (exit_status);
563 	if (exit_code != 0) {
564 		char *str;
565 		char *message;
566 
567 		/* stderr_contents is in the system locale encoding, not UTF-8 */
568 
569 		str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL);
570 
571 		if (str && str[0])
572 			message = g_strdup_printf (_("Samba's testparm returned error %d: %s"), exit_code, str);
573 		else
574 			message = g_strdup_printf (_("Samba's testparm returned error %d"), exit_code);
575 
576 		g_free (str);
577 
578 		g_set_error (error,
579 			     G_SPAWN_ERROR,
580 			     G_SPAWN_ERROR_FAILED,
581 			     "%s",
582 			     message);
583 
584 		g_free (message);
585 
586 		goto out;
587 	}
588 
589 	retval = TRUE;
590 	*supports_guest_ok_ret = (g_ascii_strncasecmp (stdout_contents, "Yes", 3) == 0);
591 
592  out:
593 	g_free (stdout_contents);
594 	g_free (stderr_contents);
595 
596 	return retval;
597 }
598 
599 static gboolean
add_share(ShareInfo * info,GError ** error)600 add_share (ShareInfo *info, GError **error)
601 {
602 	char *argv[7];
603 	int argc;
604 	ShareInfo *copy;
605 	GKeyFile *key_file;
606 	GError *real_error;
607 	gboolean supports_success;
608 	gboolean supports_guest_ok;
609 	gboolean net_usershare_success;
610 
611 	/*	g_message ("add_share() start"); */
612 
613 	if (throw_error_on_add) {
614 		g_set_error (error,
615 			     SHARES_ERROR,
616 			     SHARES_ERROR_FAILED,
617 			     _("Failed"));
618 		g_message ("add_share() end FAIL");
619 		return FALSE;
620 	}
621 
622 	supports_success = shares_supports_guest_ok (&supports_guest_ok, error);
623 	if (!supports_success)
624 		return FALSE;
625 
626 	argv[0] = "add";
627 	argv[1] = "-l";
628 	argv[2] = info->share_name;
629 	argv[3] = info->path;
630 	argv[4] = info->comment;
631 	argv[5] = info->is_writable ? "Everyone:F" : g_strdup_printf ("Everyone:R,%s:F", g_get_user_name ());
632 
633 	if (supports_guest_ok) {
634 		argv[6] = info->guest_ok ? "guest_ok=y" : "guest_ok=n";
635 		argc = 7;
636 	} else
637 		argc = 6;
638 
639 	real_error = NULL;
640 	net_usershare_success = net_usershare_run (argc, argv, &key_file, &real_error);
641 	if (!info->is_writable) g_free (argv[5]);
642 
643 	if (!net_usershare_success) {
644 		g_message ("Called \"net usershare add\" but it failed: %s", real_error->message);
645 		g_propagate_error (error, real_error);
646 		return FALSE;
647 	}
648 
649 	replace_shares_from_key_file (key_file);
650 
651 	copy = copy_share_info (info);
652 	add_share_info_to_hashes (copy);
653 
654 	/* g_message ("add_share() end SUCCESS"); */
655 
656 	return TRUE;
657 }
658 
659 static gboolean
remove_share(const char * path,GError ** error)660 remove_share (const char *path, GError **error)
661 {
662 	ShareInfo *old_info;
663 	char *argv[2];
664 	GError *real_error;
665 
666 	/* g_message ("remove_share() start"); */
667 
668 	if (throw_error_on_remove) {
669 		g_set_error (error,
670 			     SHARES_ERROR,
671 			     SHARES_ERROR_FAILED,
672 			     "Failed");
673 		g_message ("remove_share() end FAIL");
674 		return FALSE;
675 	}
676 
677 	old_info = lookup_share_by_path (path);
678 	if (!old_info) {
679 		char *display_name;
680 
681 		display_name = g_filename_display_name (path);
682 		g_set_error (error,
683 			     SHARES_ERROR,
684 			     SHARES_ERROR_NONEXISTENT,
685 			     _("Cannot remove the share for path %s: that path is not shared"),
686 			     display_name);
687 		g_free (display_name);
688 
689 		g_message ("remove_share() end FAIL: path %s was not in our hashes", path);
690 		return FALSE;
691 	}
692 
693 	argv[0] = "delete";
694 	argv[1] = old_info->share_name;
695 
696 	real_error = NULL;
697 	if (!net_usershare_run (G_N_ELEMENTS (argv), argv, NULL, &real_error)) {
698 		g_message ("Called \"net usershare delete\" but it failed: %s", real_error->message);
699 		g_propagate_error (error, real_error);
700 		g_message ("remove_share() end FAIL");
701 		return FALSE;
702 	}
703 
704 	remove_share_info_from_hashes (old_info);
705 	shares_free_share_info (old_info);
706 
707 	/* g_message ("remove_share() end SUCCESS"); */
708 
709 	return TRUE;
710 }
711 
712 static gboolean
modify_share(const char * old_path,ShareInfo * info,GError ** error)713 modify_share (const char *old_path, ShareInfo *info, GError **error)
714 {
715 	ShareInfo *old_info;
716 
717  	/* g_message ("modify_share() start"); */
718 
719 	old_info = lookup_share_by_path (old_path);
720 	if (old_info == NULL) {
721 	  /*g_message ("modify_share() end; calling add_share() instead");*/
722 		return add_share (info, error);
723 	}
724 
725 	g_assert (old_info != NULL);
726 
727 	if (strcmp (info->path, old_info->path) != 0) {
728 		g_set_error (error,
729 			     SHARES_ERROR,
730 			     SHARES_ERROR_FAILED,
731 			     _("Cannot change the path of an existing share; please remove the old share first and add a new one"));
732 		g_message ("modify_share() end FAIL: tried to change the path in a share!");
733 		return FALSE;
734 	}
735 
736 	if (throw_error_on_modify) {
737 		g_set_error (error,
738 			     SHARES_ERROR,
739 			     SHARES_ERROR_FAILED,
740 			     "Failed");
741 		g_message ("modify_share() end FAIL");
742 		return FALSE;
743 	}
744 
745 	/* Although "net usershare add" will modify an existing share if it has the same share name
746 	 * as the one that gets passed in, our semantics are different.  We have a one-to-one mapping
747 	 * between paths and share names; "net usershare" supports a one-to-many mapping from paths
748 	 * to share names.  So, we must first remove the old share and then add the new/modified one.
749 	 */
750 
751 	if (!remove_share (old_path, error)) {
752 		g_message ("modify_share() end FAIL: error when removing old share");
753 		return FALSE;
754 	}
755 
756 	/* g_message ("modify_share() end: will call add_share() with the new share info"); */
757 	return add_share (info, error);
758 }
759 
760 
761 /* Public API */
762 
763 GQuark
shares_error_quark(void)764 shares_error_quark (void)
765 {
766 	static GQuark quark;
767 
768 	if (quark == 0)
769 		quark = g_quark_from_string ("caja-share-error-quark"); /* not from_static_string since we are a module */
770 
771 	return quark;
772 }
773 
774 /**
775  * shares_free_share_info:
776  * @info: A #ShareInfo structure.
777  *
778  * Frees a #ShareInfo structure.
779  **/
780 void
shares_free_share_info(ShareInfo * info)781 shares_free_share_info (ShareInfo *info)
782 {
783 	g_assert (info != NULL);
784 
785 	g_free (info->path);
786 	g_free (info->share_name);
787 	g_free (info->comment);
788 	g_free (info);
789 }
790 
791 /**
792  * shares_get_path_is_shared:
793  * @path: A full path name ("/foo/bar/baz") in file system encoding.
794  * @ret_is_shared: Location to store result value (#TRUE if the path is shared, #FALSE otherwise)
795  * @error: Location to store error, or #NULL.
796  *
797  * Checks whether a path is shared through Samba.
798  *
799  * Return value: #TRUE if the info could be queried successfully, #FALSE
800  * otherwise.  If this function returns #FALSE, an error code will be returned in the
801  * @error argument, and *@ret_is_shared will be set to #FALSE.
802  **/
803 gboolean
shares_get_path_is_shared(const char * path,gboolean * ret_is_shared,GError ** error)804 shares_get_path_is_shared (const char *path, gboolean *ret_is_shared, GError **error)
805 {
806 	g_assert (ret_is_shared != NULL);
807 	g_assert (error == NULL || *error == NULL);
808 
809 	if (!refresh_if_needed (error)) {
810 		*ret_is_shared = FALSE;
811 		return FALSE;
812 	}
813 
814 	*ret_is_shared = (lookup_share_by_path (path) != NULL);
815 
816 	return TRUE;
817 }
818 
819 /**
820  * shares_get_share_info_for_path:
821  * @path: A full path name ("/foo/bar/baz") in file system encoding.
822  * @ret_share_info: Location to store result with the share's info - on return,
823  * will be non-NULL if the path is indeed shared, or #NULL if the path is not
824  * shared.  You must free the non-NULL value with shares_free_share_info().
825  * @error: Location to store error, or #NULL.
826  *
827  * Queries the information for a shared path:  its share name, its read-only status, etc.
828  *
829  * Return value: #TRUE if the info could be queried successfully, #FALSE
830  * otherwise.  If this function returns #FALSE, an error code will be returned in the
831  * @error argument, and *@ret_share_info will be set to #NULL.
832  **/
833 gboolean
shares_get_share_info_for_path(const char * path,ShareInfo ** ret_share_info,GError ** error)834 shares_get_share_info_for_path (const char *path, ShareInfo **ret_share_info, GError **error)
835 {
836 	ShareInfo *info;
837 
838 	g_assert (path != NULL);
839 	g_assert (ret_share_info != NULL);
840 	g_assert (error == NULL || *error == NULL);
841 
842 	if (!refresh_if_needed (error)) {
843 		*ret_share_info = NULL;
844 		return FALSE;
845 	}
846 
847 	info = lookup_share_by_path (path);
848 	*ret_share_info = copy_share_info (info);
849 
850 	return TRUE;
851 }
852 
853 /**
854  * shares_get_share_name_exists:
855  * @share_name: Name of a share.
856  * @ret_exists: Location to store return value; #TRUE if the share name exists, #FALSE otherwise.
857  *
858  * Queries whether a share name already exists in the user's list of shares.
859  *
860  * Return value: #TRUE if the info could be queried successfully, #FALSE
861  * otherwise.  If this function returns #FALSE, an error code will be returned in the
862  * @error argument, and *@ret_exists will be set to #FALSE.
863  **/
864 gboolean
shares_get_share_name_exists(const char * share_name,gboolean * ret_exists,GError ** error)865 shares_get_share_name_exists (const char *share_name, gboolean *ret_exists, GError **error)
866 {
867 	g_assert (share_name != NULL);
868 	g_assert (ret_exists != NULL);
869 	g_assert (error == NULL || *error == NULL);
870 
871 	if (!refresh_if_needed (error)) {
872 		*ret_exists = FALSE;
873 		return FALSE;
874 	}
875 
876 	*ret_exists = (lookup_share_by_share_name (share_name) != NULL);
877 
878 	return TRUE;
879 }
880 
881 /**
882  * shares_get_share_info_for_share_name:
883  * @share_name: Name of a share.
884  * @ret_share_info: Location to store result with the share's info - on return,
885  * will be non-NULL if there is a share for the specified name, or #NULL if no
886  * share has such name.  You must free the non-NULL value with
887  * shares_free_share_info().
888  * @error: Location to store error, or #NULL.
889  *
890  * Queries the information for the share which has a specific name.
891  *
892  * Return value: #TRUE if the info could be queried successfully, #FALSE
893  * otherwise.  If this function returns #FALSE, an error code will be returned in the
894  * @error argument, and *@ret_share_info will be set to #NULL.
895  **/
896 gboolean
shares_get_share_info_for_share_name(const char * share_name,ShareInfo ** ret_share_info,GError ** error)897 shares_get_share_info_for_share_name (const char *share_name, ShareInfo **ret_share_info, GError **error)
898 {
899 	ShareInfo *info;
900 
901 	g_assert (share_name != NULL);
902 	g_assert (ret_share_info != NULL);
903 	g_assert (error == NULL || *error == NULL);
904 
905 	if (!refresh_if_needed (error)) {
906 		*ret_share_info = NULL;
907 		return FALSE;
908 	}
909 
910 	info = lookup_share_by_share_name (share_name);
911 	*ret_share_info = copy_share_info (info);
912 
913 	return TRUE;
914 }
915 
916 /**
917  * shares_modify_share:
918  * @old_path: Path of the share to modify, or %NULL.
919  * @info: Info of the share to modify/add, or %NULL to delete a share.
920  * @error: Location to store error, or #NULL.
921  *
922  * Can add, modify, or delete shares.  To add a share, pass %NULL for @old_path,
923  * and a non-null @info.  To modify a share, pass a non-null @old_path and
924  * non-null @info; in this case, @info->path must have the same contents as
925  * @old_path.  To remove a share, pass a non-NULL @old_path and a %NULL @info.
926  *
927  * Return value: TRUE if the share could be modified, FALSE otherwise.  If this returns
928  * FALSE, then the error information will be placed in @error.
929  **/
930 gboolean
shares_modify_share(const char * old_path,ShareInfo * info,GError ** error)931 shares_modify_share (const char *old_path, ShareInfo *info, GError **error)
932 {
933 	g_assert ((old_path == NULL && info != NULL)
934 		  || (old_path != NULL && info == NULL)
935 		  || (old_path != NULL && info != NULL));
936 	g_assert (error == NULL || *error == NULL);
937 
938 	if (!refresh_if_needed (error))
939 		return FALSE;
940 
941 	if (old_path == NULL)
942 		return add_share (info, error);
943 	else if (info == NULL)
944 		return remove_share (old_path, error);
945 	else
946 		return modify_share (old_path, info, error);
947 }
948 
949 static void
copy_to_slist_cb(gpointer key,gpointer value,gpointer data)950 copy_to_slist_cb (gpointer key, gpointer value, gpointer data)
951 {
952 	ShareInfo *info;
953 	ShareInfo *copy;
954 	GSList **list;
955 
956 	info = value;
957 	list = data;
958 
959 	copy = copy_share_info (info);
960 	*list = g_slist_prepend (*list, copy);
961 }
962 
963 /**
964  * shares_get_share_info_list:
965  * @ret_info_list: Location to store the return value, which is a list
966  * of #ShareInfo structures.  Free this with shares_free_share_info_list().
967  * @error: Location to store error, or #NULL.
968  *
969  * Gets the list of shared folders and their information.
970  *
971  * Return value: #TRUE if the info could be queried successfully, #FALSE
972  * otherwise.  If this function returns #FALSE, an error code will be returned in the
973  * @error argument, and *@ret_info_list will be set to #NULL.
974  **/
975 gboolean
shares_get_share_info_list(GSList ** ret_info_list,GError ** error)976 shares_get_share_info_list (GSList **ret_info_list, GError **error)
977 {
978 	g_assert (ret_info_list != NULL);
979 	g_assert (error == NULL || *error == NULL);
980 
981 	if (!refresh_if_needed (error)) {
982 		*ret_info_list = NULL;
983 		return FALSE;
984 	}
985 
986 	*ret_info_list = NULL;
987 	g_hash_table_foreach (path_share_info_hash, copy_to_slist_cb, ret_info_list);
988 
989 	return TRUE;
990 }
991 
992 /**
993  * shares_free_share_info_list:
994  * @list: List of #ShareInfo structures, or %NULL.
995  *
996  * Frees a list of #ShareInfo structures as returned by shares_get_share_info_list().
997  **/
998 void
shares_free_share_info_list(GSList * list)999 shares_free_share_info_list (GSList *list)
1000 {
1001 	GSList *l;
1002 
1003 	for (l = list; l; l = l->next) {
1004 		shares_free_share_info (l->data);
1005 	}
1006 
1007 	g_slist_free (list);
1008 }
1009 
1010 void
shares_set_debug(gboolean error_on_refresh,gboolean error_on_add,gboolean error_on_modify,gboolean error_on_remove)1011 shares_set_debug (gboolean error_on_refresh,
1012 		  gboolean error_on_add,
1013 		  gboolean error_on_modify,
1014 		  gboolean error_on_remove)
1015 {
1016 	throw_error_on_refresh = error_on_refresh;
1017 	throw_error_on_add = error_on_add;
1018 	throw_error_on_modify = error_on_modify;
1019 	throw_error_on_remove = error_on_remove;
1020 }
1021