diff -ur sitecopy-0.10.15.org/src/common.c sitecopy-0.10.15.new/src/common.c
--- sitecopy-0.10.15.org/src/common.c	Wed Aug  2 06:57:38 2000
+++ sitecopy-0.10.15.new/src/common.c	Wed Mar 14 14:35:17 2001
@@ -56,6 +56,7 @@
     { "httpauth", DEBUG_HTTPAUTH },
     { "httpbody", DEBUG_HTTPBODY },
     { "cleartext", DEBUG_HTTPPLAIN },
+    { "rsh", DEBUG_RSH },
     { NULL, 0 }
 };
 
diff -ur sitecopy-0.10.15.org/src/common.h sitecopy-0.10.15.new/src/common.h
--- sitecopy-0.10.15.org/src/common.h	Thu Jan  4 06:55:45 2001
+++ sitecopy-0.10.15.new/src/common.h	Wed Mar 14 14:34:43 2001
@@ -40,6 +40,7 @@
 #define DEBUG_FTP (1<<12)
 #define DEBUG_GNOME (1<<13)
 #define DEBUG_FILESEXTRA (1<<13)
+#define DEBUG_RSH (1<<14)
 
 /* A signal hander */
 typedef void (*sig_handler)(int);
diff -ur sitecopy-0.10.15.org/src/rshdriver.c sitecopy-0.10.15.new/src/rshdriver.c
--- sitecopy-0.10.15.org/src/rshdriver.c	Mon Jan  8 02:45:10 2001
+++ sitecopy-0.10.15.new/src/rshdriver.c	Wed Mar 14 23:14:18 2001
@@ -52,89 +52,70 @@
     const char *rsh_cmd, *rcp_cmd;
     char buf[BUFSIZ];
     char err[BUFSIZ];
+    FILE *fp;
 } rsh_session;
 
-static int run_rsh(rsh_session *sess, const char *template, ...) 
-#ifdef __GNUC__
-                __attribute__ ((format (printf, 2, 3)))
-#endif /* __GNUC__ */
-    ;
+enum rcommand { RCP, RSH, RSH_PIPE_READ, RSH_PIPE_WRITE };
+
+int
+rsh_fetch(rsh_session *sess, const char *startdir, struct proto_file **files);
 
-static int run_rcp(rsh_session *sess, const char *template, ...) 
+static int run_rcpsh(enum rcommand cpsh,
+                     rsh_session *sess, const char *template, ...) 
 #ifdef __GNUC__
-                __attribute__ ((format (printf, 2, 3)))
+                __attribute__ ((format (printf, 3, 4)))
 #endif /* __GNUC__ */
     ;
 
-static int run_rsh(rsh_session *sess, const char *template, ...) 
+static int run_rcpsh(enum rcommand cpsh,
+                     rsh_session *sess, const char *template, ...) 
 {
     va_list params;
-    char *pnt;
+    char *buf = sess->buf;
     size_t len;
-    int written;
-    
-    if (sess->site->server.username) {
-	len = 1 + strlen(sess->rsh_cmd) + 
-	    strlen(sess->site->server.username) +
-	    strlen(sess->site->server.hostname) +
-	    strlen(" -l   \"");
-	if (len>=BUFSIZ)
-	    return SITE_FAILED;
-	snprintf(sess->buf, len, "%s -l %s %s \"", 
-		 sess->rsh_cmd,
-		 sess->site->server.username,
-		 sess->site->server.hostname);
+
+    if (cpsh == RCP) {
+        len = snprintf(buf, BUFSIZ, "%s ", sess->rcp_cmd);
     } else {
-	len = 1 + strlen(sess->rsh_cmd) + 
-	    strlen(sess->site->server.hostname) +
-	    strlen("  \"");
-	if (len>=BUFSIZ)
-	    return SITE_FAILED;
-	snprintf(sess->buf, len, "%s %s \"", 
-		 sess->rsh_cmd,
-		 sess->site->server.hostname);
+        char *username = sess->site->server.username;
+        len = snprintf(buf, BUFSIZ, "%s%s%s %s \\\\", 
+                       sess->rsh_cmd,
+                       (username != NULL ? " -l " : ""),
+                       (username != NULL ? username : ""),
+                       sess->site->server.hostname);
     }
-    pnt = sess->buf + len - 1;
-    
+
     va_start(params, template);
 #ifdef HAVE_VSNPRINTF
-    written = vfsnprintf(pnt, BUFSIZ - len, template, params);
+    len += vfsnprintf(buf + len, BUFSIZ - len, template, params);
 #else
-    written = vsprintf(pnt, template, params);
+    len += vsprintf(buf + len, template, params);
 #endif
     va_end(params);
 
-    pnt += written;
-    *pnt = '"';
-    
-    if (system(sess->buf) == 0) {
-	return SITE_OK;
+    len += snprintf(buf + len, BUFSIZ - len, " 2> /dev/null" );
+    if (len + 1 >= BUFSIZ) return SITE_FAILED;  /* buff over flow */
+    DEBUG(DEBUG_RSH, "\nr-command: %s\n", buf);
+
+    if (cpsh == RSH_PIPE_READ || cpsh == RSH_PIPE_WRITE) {
+        sess->fp = popen(buf, (cpsh==RSH_PIPE_READ ? "r" : "w"));
+        if (sess->fp != NULL) {
+            return SITE_OK;
+        } else {
+            return SITE_FAILED;
+        }
     } else {
-	return SITE_FAILED;
+        if (system(buf) == 0) {
+            return SITE_OK;
+        } else {
+            return SITE_FAILED;
+        }
     }
 }
 
-static int run_rcp(rsh_session *sess, const char *template, ...) 
+static int run_finish(rsh_session *sess)
 {
-    va_list params;
-    char *pnt;
-    size_t len;
-    
-    strcpy(sess->buf, sess->rcp_cmd);
-    len = strlen(sess->rcp_cmd);
-    pnt = sess->buf + len;
-    
-    *pnt++ = ' ';
-
-    va_start(params, template);
-#ifdef HAVE_VSNPRINTF
-    vfsnprintf(pnt, BUFSIZ - len, template, params);
-#else
-    vsprintf(pnt, template, params);
-#endif
-    va_end(params);
-
-    if (system(sess->buf) == 0) {
+    if (pclose(sess->fp) == 0) {
 	return SITE_OK;
     } else {
 	return SITE_FAILED;
@@ -167,7 +148,7 @@
 
 static int file_move(void *session, const char *from, const char *to) {
     rsh_session *sess = session;
-    return run_rsh(sess, "mv %s %s", from, to);
+    return run_rcpsh(RSH, sess, "mv '%s' '%s'", from, to);
 }
 
 static int file_upload(void *session, const char *local, const char *remote, 
@@ -175,12 +156,12 @@
     rsh_session *sess = session;
 
     if (sess->site->server.username) {
-	return run_rcp(sess, "%s %s@%s:%s",
+	return run_rcpsh(RCP, sess, "'%s' '%s@%s:%s'",
 		       local, sess->site->server.username,
 		       sess->site->server.hostname, remote);
     }
     else {
-	return run_rcp(sess, "%s %s:%s", local, 
+	return run_rcpsh(RCP, sess, "'%s' '%s:%s'", local, 
 		       sess->site->server.hostname, remote);
     }
 }
@@ -200,34 +181,60 @@
     return SITE_UNSUPPORTED;
 }
 
-static int file_read(void *sess, const char *remote,
+static int file_read(void *session, const char *remote,
 		     sock_block_reader reader, void *userdata) {
-    return SITE_UNSUPPORTED;
+    rsh_session *sess = session;
+    int ret;
+
+    ret = run_rcpsh(RSH_PIPE_READ, sess, "cat '%s'", remote);
+    if (ret == SITE_OK) {
+        size_t len;
+        while ( (len = fread(sess->buf, sizeof(char), BUFSIZ, sess->fp))
+                > 0 ) {
+            reader(userdata, sess->buf, len);
+        }
+        return run_finish(sess);
+    }
+    return SITE_FAILED;
 }
 
 static int file_delete(void *session, const char *filename) {
     rsh_session *sess = session;
-    return run_rsh(sess, "rm %s", filename);
+    return run_rcpsh(RSH, sess, "rm '%s'", filename);
 }
 
 static int file_chmod(void *session, const char *fname, const mode_t mode) {
     rsh_session *sess = session;
-    return run_rsh(sess, "chmod %03o %s", mode, fname);
+    return run_rcpsh(RSH, sess, "chmod %03o '%s'", mode, fname);
 }
 
 static int dir_create(void *session, const char *dirname) {
     rsh_session *sess = session;
-    return run_rsh(sess, "mkdir %s", dirname);
+    return run_rcpsh(RSH, sess, "mkdir '%s'", dirname);
 }
 
 static int dir_remove(void *session, const char *dirname) {
     rsh_session *sess = session;
-    return run_rsh(sess, "rmdir %s", dirname);
+    return run_rcpsh(RSH, sess, "rmdir '%s'", dirname);
 }
 
-static int fetch_list(void *sess, const char *dirname, int need_modtimes,
+static int fetch_list(void *session, const char *dirname, int need_modtimes,
 		       struct proto_file **files) {
-    return SITE_UNSUPPORTED;
+    rsh_session *sess = session;
+    int ret;
+
+    ret = run_rcpsh(RSH_PIPE_READ, sess, "ls -laR '%s'", dirname);
+    if (ret == SITE_OK) {
+        ret = rsh_fetch(sess, dirname, files);
+    }
+
+    /*
+    if (ret == SITE_OK && need_modtimes) {
+	ret = rsh_fetch_modtimes(sess, dirname, *files);
+    }
+    */
+
+    return ret;
 }
 
 static const char *error(void *session) {
@@ -262,3 +269,152 @@
     get_dummy_port,
     "rsh/rcp"
 };
+
+
+#include <string_utils.h>
+/* Decode a ls -l permissions string. */
+static mode_t rsh_decode_perms(const char *perms) {
+    mode_t ret = 0;
+    int n;
+    for (n = 0; n < 9; n++) {
+	if (perms[1+n] != '-') ret |= 1<<(8-n);
+    }
+    DEBUG(DEBUG_RSH, "Decoded [%s] is %o\n", perms, ret);
+    return ret;
+}
+
+/* This does the ls -laR, and tries it's best to parse the resulting
+ * list. Currently implemented only for Unix-style servers, which go:
+ * dirlist
+ * new/directory/name:
+ * dirlist
+ * another/directory/name:
+ * dirlist
+ * etc. where dirlist is a straight ls -al listing
+ */
+int
+rsh_fetch(rsh_session *sess, const char *startdir, struct proto_file **files) 
+{
+    struct proto_file *this_file, *last_file;
+    char *curdir,   /* Holds the path of the current directory */
+	*buffer = sess->buf,
+	perms[16], filename[BUFSIZ], tmp[BUFSIZ], *topdir;
+    int ret, filesize, buflen, success = SITE_OK;
+    int afterblank;
+
+    memset(buffer, 0, BUFSIZ);
+    
+    /* The current directory is a 0-length string. */
+    curdir = ne_strdup("");
+    last_file = NULL;
+
+    topdir = strdup(startdir);
+    
+    /* Get rid of the trailing slash. */
+    if (topdir[strlen(topdir)-1] == '/') {
+	topdir[strlen(topdir)-1] = '\0';
+    }
+
+    DEBUG(DEBUG_RSH, "Top dir is [%s]\n", topdir);
+
+    afterblank = false;
+    while (fgets(buffer, BUFSIZ, sess->fp) != NULL) {
+	STRIP_EOL(buffer);
+	DEBUG(DEBUG_RSH, "[ls] < %s\n", buffer);
+	buflen = strlen(buffer);
+	if (buflen == 0) {
+	    DEBUG(DEBUG_RSH, "Blank line.\n");
+	    afterblank = true;
+	    continue;
+	} else if (strncmp(buffer, "total ", 6) == 0) {
+	    /* ignore the line */
+	    DEBUG(DEBUG_RSH, "Line ignored.\n");
+	} else if (buffer[buflen-1] == ':' && 
+		   (afterblank || (strchr(buffer, ' ') == NULL))) {
+	    /* A new directory name indicator, which goes:
+	     *    `directory/name/here:'
+	     * We want directory names as:
+	     *    `directory/name/here/' 
+	     * Hence a bit of messing about. */
+	    free(curdir);
+	    curdir = buffer;
+	    /* Skip a leading Windows drive specification */
+	    if (strlen(curdir) > 3 &&
+		isalpha(curdir[0]) && curdir[1] == ':' && curdir[2] == '/') {
+		curdir += 2;
+	    }
+	    if (strncmp(curdir, topdir, strlen(topdir)) == 0) {
+		curdir += strlen(topdir);
+	    }
+	    /* Skip a single . if .:  */
+	    if (strcmp(curdir,".:") == 0) {
+		curdir++;
+	    }
+	    /* Skip a leading "./" */
+	    if (strncmp(curdir, "./", 2) == 0) {
+		curdir += 2;
+	    }
+	    /* Skip repeated '/' characters */
+	    while (*curdir == '/') curdir++;
+	    curdir = ne_strdup(curdir);
+	    if (strlen(curdir) > 1) {
+		curdir[strlen(curdir)-1] = '/';  /* change ':' to '/' */
+	    } else {
+		/* this is just the top-level directory... */
+		curdir[0] = '\0';
+	    }
+	    DEBUG(DEBUG_RSH, "Now in directory: [%s]\n", curdir);
+	} else {
+	    /* Weird bit at the end should pick up everything
+	     * to the EOL... filenames could have whitespace in.
+	     */
+	    sscanf(buffer, "%15s %s %s %s %d %s %s %s %[^*]",  
+		   perms, tmp, tmp, tmp, &filesize,
+		   tmp, tmp, tmp, filename);
+	    if (perms!=NULL && filename!=NULL && strlen(filename) > 0) {
+		if (*perms == '-') {
+		    /* Normal file. */
+		    DEBUG(DEBUG_RSH, "File: %s, size %d\n",
+			   filename, filesize);
+		    this_file = ne_calloc(sizeof(struct proto_file));
+		    this_file->next = *files;
+		    this_file->mode = rsh_decode_perms(perms);
+		    *files = this_file;
+		    if (last_file==NULL) last_file = this_file;
+		    CONCAT2(this_file->filename, curdir, filename);
+		    this_file->type = proto_file;
+		    this_file->size = filesize;
+		} else if (*perms == 'd') {
+		    /* Subdirectory */
+		    if (strcmp(filename, ".") == 0 ||
+			strcmp(filename, "..") == 0) {
+			DEBUG(DEBUG_RSH, "Ignoring: %s\n", filename);
+		    } else {
+			DEBUG(DEBUG_RSH, "Subdir: %s\n", filename);
+			this_file = ne_calloc(sizeof(struct proto_file));
+			if (last_file==NULL) {
+			    *files = this_file;
+			} else {
+			    last_file->next = this_file;
+			}
+			last_file = this_file;
+			CONCAT2(this_file->filename, curdir, filename);
+			this_file->type = proto_dir;
+		    }
+		} else { 
+		    /* Summant else... ignore */
+		    DEBUG(DEBUG_RSH, "Ignoring: %s\n", filename);
+		}
+	    } else {
+		DEBUG(DEBUG_RSH, "Could not parse line.\n");
+		success = SITE_FAILED;
+		break;
+	    }
+	}
+    }
+
+    free(topdir);
+
+    DEBUG(DEBUG_RSH, "Fetch finished successfully.\n");
+    run_finish(sess);
+}
