PATCH: safe recursion in smbclient

Justin Yackoski justin at skiingyac.com
Mon Apr 29 09:04:02 GMT 2002


This patch adds a saferecurse option to smbclient which hashes directory
contents to check for infinite recursion, similar to wget.  It was
written to require minimal changes to client.c and its behavior while
still preventing infinite recursion.  It doesn't change the default
behavior of smbclient, and plays well with the normal recurse option.  

This fixes a problem with those who want to retrieve file lists off of
servers where circular file systems may or do exist (in either windows
or *nix).  This is useful if someone either desires a circular file
system on a share or if they have little/no control over the file
structure of the server.

I have tested it with directories containing 30,000+ files and saw less
than a second of performance loss versus normal recursion, so I believe
this is a worthwhile tradeoff.  Please accept this patch, it will solve
many problems.  I submit that the regular unsafe recursion option should
be removed (or renamed to unsaferecurse and this to recurse) but this
patch allows both to be available and only adds saferecurse, changing no
behavior otherwise, since I realize others may disagree.

This is a patch against 2.2.3a but I've checked in cvs and it should
work with any revision after that as well since not much has changed.

Thank you.
Justin Yackoski


--- client/client.c	Sat Feb  2 19:46:38 2002
+++ client/client.c	Fri Feb  8 18:28:06 2002
@@ -79,6 +79,10 @@
 int printmode = 1;
 
 static BOOL recurse = False;
+static BOOL saferecurse = False;
+static hash_table saferecurse_hash_table;
+static int dirs_added = 0;
+char dummy_value[] = "a";
 BOOL lowercase = False;
 
 struct in_addr dest_ip;
@@ -346,14 +350,97 @@
 }
 
 static BOOL do_list_recurse;
+static BOOL do_list_saferecurse;
 static BOOL do_list_dirs;
 static char *do_list_queue = 0;
 static long do_list_queue_size = 0;
 static long do_list_queue_start = 0;
 static long do_list_queue_end = 0;
+static char *saferecurse_current_dir = 0;
+static long saferecurse_current_dir_size = 0;
+static long saferecurse_current_dir_end = 0;
+static long saferecurse_current_dir_start = 0;
 static void (*do_list_fn)(file_info *);
 
 /****************************************************************************
+functions for saferecurse_current_dir
+  ****************************************************************************/
+
+/*
+ * saferecurse_current_dir is a string with the name, size, and mtime of
+ * all files and directories in the current directory, which will later
+ * be hashed if saferecurse is being used
+ */
+static void reset_saferecurse_hash_table( void )
+{
+	static BOOL initialised;
+	if (initialised) {
+		hash_clear(&saferecurse_hash_table);
+	}
+
+	initialised = hash_table_init( &saferecurse_hash_table, 512, 
+				       (compare_function)(strcmp));
+}
+
+static void reset_saferecurse_current_dir(void)
+{
+	SAFE_FREE(saferecurse_current_dir);
+	saferecurse_current_dir = 0;
+	saferecurse_current_dir_size = 0;
+	saferecurse_current_dir_start = 0;
+	saferecurse_current_dir_end = 0;
+}
+
+static void init_saferecurse_current_dir(void)
+{
+	reset_saferecurse_current_dir();
+	saferecurse_current_dir_size = 1024;
+	saferecurse_current_dir = malloc(saferecurse_current_dir_size);
+	if (saferecurse_current_dir == 0) { 
+		DEBUG(0,("malloc fail for size %d\n",
+			 (int)saferecurse_current_dir_size));
+		reset_saferecurse_current_dir();
+	} else {
+		memset(saferecurse_current_dir, 0,
+                    saferecurse_current_dir_size);
+	}
+}
+
+static void add_saferecurse_current_dir(const char* entry)
+{
+	char *dlq;
+
+	long new_end = saferecurse_current_dir_end + ((long)strlen(entry)) + 1;
+	while (new_end > saferecurse_current_dir_size)
+	{
+		saferecurse_current_dir_size *= 2;
+		DEBUG(4,("enlarging saferecurse_current_dir to %d\n",
+			 (int)saferecurse_current_dir_size));
+		dlq = Realloc(saferecurse_current_dir,
+                    saferecurse_current_dir_size);
+		if (!dlq) {
+			DEBUG(0,("failure enlarging saferecurse_current_dir to %d bytes\n",
+				(int)saferecurse_current_dir_size));
+			reset_saferecurse_current_dir();
+		} else {
+			saferecurse_current_dir = dlq;
+			memset(saferecurse_current_dir +
+                            saferecurse_current_dir_size / 2,
+			    0, saferecurse_current_dir_size / 2);
+		}
+	}
+	if (saferecurse_current_dir)
+	{
+		pstrcpy(saferecurse_current_dir + saferecurse_current_dir_end,
+                    entry);
+		saferecurse_current_dir_end = new_end;
+		DEBUG(4,("added %s to saferecurse_current_dir (start=%d, end=%d)\n",
+			 entry, (int)saferecurse_current_dir_start,
+                         (int)saferecurse_current_dir_end));
+	}
+}
+
+/****************************************************************************
 functions for do_list_queue
   ****************************************************************************/
 
@@ -471,6 +558,7 @@
   ****************************************************************************/
 static void do_list_helper(file_info *f, const char *mask, void *state)
 {
+  char *c;
 	if (f->mode & aDIR) {
 		if (do_list_dirs && do_this_one(f)) {
 			do_list_fn(f);
@@ -488,6 +576,10 @@
 			pstrcat(mask2, f->name);
 			pstrcat(mask2,"\\*");
 			add_to_do_list_queue(mask2);
+                        if (saferecurse)
+                        {
+                          dirs_added++;
+                        }
 		}
 		return;
 	}
@@ -495,6 +587,12 @@
 	if (do_this_one(f)) {
 		do_list_fn(f);
 	}
+        asprintf(&c, "%s%d%d", f->name, f->size, f->mtime);
+        if (saferecurse)
+        {
+          add_saferecurse_current_dir(c);
+        }
+        SAFE_FREE(c);
 }
 

@@ -505,6 +603,11 @@
 {
 	static int in_do_list = 0;
 
+        if (saferecurse)
+        {
+          reset_saferecurse_hash_table();
+        }
+        
 	if (in_do_list && rec)
 	{
 		fprintf(stderr, "INTERNAL ERROR: do_list called recursively when the recursive flag is true\n");
@@ -533,7 +636,39 @@
 			 */
 			pstring head;
 			pstrcpy(head, do_list_queue_head());
+                        if (saferecurse)
+                        {
+                          init_saferecurse_current_dir();
+                          dirs_added = 0;
+                        }
 			cli_list(cli, head, attribute, do_list_helper, NULL);
+                        if (saferecurse)
+                        {
+                          char hashkey[16];
+                          mdfour(hashkey, saferecurse_current_dir,
+                              saferecurse_current_dir_end);
+                          if (hash_lookup(&saferecurse_hash_table, hashkey) != NULL)
+                          {
+                            /* must remove last dirs_added dirs we queued 
+                             * which are all the subdirs of the current dir
+                             * since a match in the hash table means we've
+                             * seen this dir before.  Still list its contents
+                             * but don't go into its subdirs. Maybe not the
+                             * optimal way but to not list contents would mean
+                             * buffering output until we know the dir hasn't
+                             * been seen */
+                            DEBUG(3,("unsafe dir found, de-queueing subdirs"));
+                            while (dirs_added > 0)
+                            {
+                              remove_do_list_queue_head();
+                              dirs_added--;
+                            }
+                          }
+                          else
+                          {
+                            hash_insert(&saferecurse_hash_table, dummy_value, hashkey);
+                          }
+                        }
 			remove_do_list_queue_head();
 			if ((! do_list_queue_empty()) && (fn == display_finfo))
 			{
@@ -1732,10 +1867,24 @@
 static void cmd_recurse(void)
 {
 	recurse = !recurse;
+        if (!recurse) //turning off recursion also turns off saferecurse flag
+        {
+          saferecurse = False;
+        }
 	DEBUG(2,("directory recursion is now %s\n",recurse?"on":"off"));
 }
 
 /****************************************************************************
+toggle the saferecurse flag
+****************************************************************************/
+static void cmd_saferecurse(void)
+{
+	saferecurse = !saferecurse;
+        recurse = saferecurse; //also turn on/off recurse flag
+	DEBUG(2,("safe directory recursion is now %s\n",saferecurse?"on":"off"));
+}
+
+/****************************************************************************
 toggle the translate flag
 ****************************************************************************/
 static void cmd_translate(void)
@@ -1921,6 +2070,7 @@
   {"quit",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
   {"rd",cmd_rmdir,"<directory> remove a directory",{COMPL_NONE,COMPL_NONE}},
   {"recurse",cmd_recurse,"toggle directory recursion for mget and mput",{COMPL_NONE,COMPL_NONE}},  
+  {"saferecurse",cmd_saferecurse,"toggle safe directory recursion for mget and mput",{COMPL_NONE,COMPL_NONE}},  
   {"rename",cmd_rename,"<src> <dest> rename some files",{COMPL_REMOTE,COMPL_REMOTE}},
   {"rm",cmd_del,"<mask> delete all matching files",{COMPL_REMOTE,COMPL_NONE}},
   {"rmdir",cmd_rmdir,"<directory> remove a directory",{COMPL_NONE,COMPL_NONE}},





More information about the samba-technical mailing list