]> git.sesse.net Git - betaftpd/blobdiff - cmds.c
Fixed a security problem where the custom snprintf() would always be used. Thanks...
[betaftpd] / cmds.c
diff --git a/cmds.c b/cmds.c
index 629d11411156082cea5cad865e077f1ab67ed906..2e38823ab9d4716ae5c2f6aae5805e513c819fca 100644 (file)
--- a/cmds.c
+++ b/cmds.c
@@ -2,7 +2,7 @@
     Copyright (C) 1999-2000 Steinar H. Gunderson
 
     This program is is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License, version 2 if the
+    it under the terms of the GNU General Public License, version 2 of the
     License as published by the Free Software Foundation.
 
     This program is distributed in the hope that it will be useful,
 #include <stropts.h>
 #endif
 
+#if HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
 #if HAVE_SYS_CONF_H
 #include <sys/conf.h>
 #endif
@@ -145,7 +149,7 @@ extern fd_set master_fds, master_send_fds;
 struct handler {
        char cmd_name[6];
        char add_cmlen;         /* =1 if the command takes an argument */
-       int (*callback)(struct conn * const);
+       void (*callback)(struct conn * const);
        char min_auth;
 #if !WANT_NONROOT
        char do_setuid;         /* =1 if root is not *really* needed */
@@ -259,7 +263,7 @@ int do_chdir(struct conn * const c, const char * const newd)
  *             authentication work. User names are limited to 16
  *             characters, by force...
  */
-int cmd_user(struct conn * const c)
+void cmd_user(struct conn * const c)
 {
        strncpy(c->username, c->recv_buf, 16);
        c->username[16] = 0;
@@ -274,7 +278,6 @@ int cmd_user(struct conn * const c)
                numeric(c, 331, "Password required for %s.", c->username);
                c->auth = 2;
        }
-       return 1;
 }
 
 /*
@@ -285,7 +288,7 @@ int cmd_user(struct conn * const c)
  *             don't even support PAM or real shadow passwords (with
  *             expiry etc) yet...
  */
-int cmd_pass(struct conn * const c)
+void cmd_pass(struct conn * const c)
 {
 #if WANT_NONROOT
        c->auth = nr_userinfo(c->username, &c->uid, c->curr_dir, c->root_dir,
@@ -305,6 +308,7 @@ int cmd_pass(struct conn * const c)
                c->auth = 0;
        } else {
                c->uid = p->pw_uid;
+               c->gid = p->pw_gid;
                strncpy(c->curr_dir, p->pw_dir, 254);
                c->curr_dir[254] = 0;
        }
@@ -324,7 +328,7 @@ int cmd_pass(struct conn * const c)
                ) {
                        c->auth = 0;
                } else {
-                       c->auth = 3;
+                       c->auth = 4;
                }
        }
 #endif /* !WANT_NONROOT */
@@ -340,9 +344,9 @@ int cmd_pass(struct conn * const c)
                chdir(c->curr_dir);
                dump_file(c, 230, "welcome.msg");
 #endif
+               /* Have a different message for anonymous users? */
                numeric(c, 230, "User logged in.");
        }
-       return 1;
 }
 
 /*
@@ -356,10 +360,9 @@ int cmd_pass(struct conn * const c)
  *             I feel that the RFC959 intention is having it _before_
  *             USER/PASS. Therefore, this one runs with root privilegies :-)
  */
-int cmd_acct(struct conn * const c)
+void cmd_acct(struct conn * const c)
 {
        numeric(c, 202, "ACCT ignored OK -- not applicable on this system.");
-       return 1;
 }
 
 /*
@@ -369,7 +372,7 @@ int cmd_acct(struct conn * const c)
  *             the whole way), in case there are some weird overflows
  *             somewhere.
  */
-int cmd_port(struct conn * const c)
+void cmd_port(struct conn * const c)
 {
        short int a0, a1, a2, a3, p0, p1;
        int i, sock, err;
@@ -378,11 +381,11 @@ int cmd_port(struct conn * const c)
     
        if ((c->transfer != NULL) && (c->transfer->state >= 4)) {
                numeric(c, 500, "Sorry, only one transfer at a time.");
-               return 1;
+               return;
        }
 
        sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-       TRAP_ERROR(sock == -1, 500, return 1);
+       TRAP_ERROR(sock == -1, 500, return);
 
        destroy_ftran(c->transfer);
        c->transfer = f = alloc_new_ftran(sock, c);
@@ -397,7 +400,7 @@ int cmd_port(struct conn * const c)
                /* bind to own address, port 20 (FTP data) */
                tmp = sizeof(sin);
                err = getsockname(c->sock, (struct sockaddr *)&sin, &tmp);
-               TRAP_ERROR(err == -1, 500, return 1);
+               TRAP_ERROR(err == -1, 500, return);
                sin.sin_port = FTP_PORT - 1;
 
                numeric(c, 200, "PORT command OK.");
@@ -406,9 +409,11 @@ int cmd_port(struct conn * const c)
 #if !WANT_NONROOT
                /* need root privilegies for a short while */
                seteuid(getuid());
+               setegid(getgid());
 #endif
                bind(sock, (struct sockaddr *)&sin, sizeof(sin));
 #if !WANT_NONROOT
+               setegid(c->gid);
                seteuid(c->uid);
 #endif
 
@@ -427,14 +432,13 @@ int cmd_port(struct conn * const c)
                i = 1;          
                ioctl(f->sock, FIONBIO, &one);
        }
-       return 1;
 }
 
 /*
  * cmd_pasv(): Handles the PASV command, and sets up the data socket.
  *             Uses port numbers > 1024, since it doesn't run as root.
  */
-int cmd_pasv(struct conn * const c)
+void cmd_pasv(struct conn * const c)
 {
        struct ftran *f;
        int tmp, sock, err;
@@ -443,14 +447,14 @@ int cmd_pasv(struct conn * const c)
 
        if ((c->transfer != NULL) && (c->transfer->state >= 4)) {
                numeric(c, 503, "Sorry, only one transfer at once.");
-               return 1;
+               return;
        }
        destroy_ftran(c->transfer);
 
        sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-       TRAP_ERROR(sock == -1, 500, return 1);
+       TRAP_ERROR(sock == -1, 500, return);
        err = add_fd(sock, POLLIN);
-       TRAP_ERROR(err != 0, 501, return 1);
+       TRAP_ERROR(err != 0, 501, return);
 
        c->transfer = f = alloc_new_ftran(sock, c);
 
@@ -459,18 +463,18 @@ int cmd_pasv(struct conn * const c)
        /* setup socket */
        tmp = sizeof(addr);
        err = getsockname(c->sock, (struct sockaddr *)&addr, &tmp);
-       TRAP_ERROR(err == -1, 500, return 1);
+       TRAP_ERROR(err == -1, 500, return);
 
        addr.sin_port = 0;      /* let the system choose */
        err = bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr));
-       TRAP_ERROR(err == -1, 500, return 1);
+       TRAP_ERROR(err == -1, 500, return);
 
        tmp = sizeof(addr);
        err = getsockname(sock, (struct sockaddr *)&addr, &tmp);
-       TRAP_ERROR(err == -1, 500, return 1);
+       TRAP_ERROR(err == -1, 500, return);
 
        err = listen(f->sock, 1);
-       TRAP_ERROR(err == -1, 500, return 1);
+       TRAP_ERROR(err == -1, 500, return);
        f->state = 1;
 
        numeric(c, 227, "Entering passive mode (%u,%u,%u,%u,%u,%u)",
@@ -480,7 +484,6 @@ int cmd_pasv(struct conn * const c)
                (htonl(addr.sin_addr.s_addr) & 0x000000ff),
                (htons(addr.sin_port) & 0xff00) >> 8,
                (htons(addr.sin_port) & 0x00ff));
-       return 1;
 }
 
 /*
@@ -491,7 +494,7 @@ int cmd_pasv(struct conn * const c)
  *             /betaftpd.users file, if you use nonroot. If not, it's a bug.
  *             Try to get it _reproducible_, and mail it to me.
  */
-int cmd_pwd(struct conn * const c)
+void cmd_pwd(struct conn * const c)
 {
        char temp[512], *cdir = NULL;
 
@@ -499,7 +502,6 @@ int cmd_pwd(struct conn * const c)
        if (cdir != NULL) {
                numeric(c, 257, "\"%s\" is current working directory.", cdir);
        }
-       return 1;
 }
 
 /*
@@ -514,6 +516,7 @@ char *do_pwd(struct conn * const c, char * const retbuf, const char * const dir)
        char *cdir = NULL;
 
        strcpy(retbuf, dir);
+
        if (strncmp(retbuf, c->root_dir, strlen(c->root_dir)) != 0) {
                numeric(c, 550, "curr_dir is outside root_dir, please contact site administrator.");
                return NULL;
@@ -533,10 +536,9 @@ char *do_pwd(struct conn * const c, char * const retbuf, const char * const dir)
  * cmd_cwd():  Handles CWD command (change working directory). Uses
  *             cmd_cwd_internal() (see below).
  */
-int cmd_cwd(struct conn * const c)
+void cmd_cwd(struct conn * const c)
 {
        cmd_cwd_internal(c, c->recv_buf);
-       return 1;
 }
 
 /*
@@ -549,10 +551,9 @@ int cmd_cwd(struct conn * const c)
  *             an error, instead of just staying in the root directory (as
  *             the OS and thus wu-ftpd does).
  */
-int cmd_cdup(struct conn * const c)
+void cmd_cdup(struct conn * const c)
 {
        cmd_cwd_internal(c, "..");
-       return 1;
 }
 
 /*
@@ -587,11 +588,10 @@ void cmd_cwd_internal(struct conn * const c, const char * const newd)
  *             sending functions to start at the correct number. We should
  *             perhaps add some better error checking to this?
  */
-int cmd_rest(struct conn * const c)
+void cmd_rest(struct conn * const c)
 {
        c->rest_pos = abs(atoi(c->recv_buf));
        numeric(c, 350, "Setting resume at %u bytes.", c->rest_pos);
-       return 1;
 }
 
 /*
@@ -601,19 +601,19 @@ int cmd_rest(struct conn * const c)
  *             connection occurs (or succeeds, if we're using PORT mode),
  *             the actual file transfer begins.
  */
-int cmd_retr(struct conn * const c)
+void cmd_retr(struct conn * const c)
 {
        struct ftran *f = c->transfer;
 
        if ((f == NULL) || ((f->state != 1) && (f->state != 3))) {
                numeric(c, 425, "No data connection set up; please use PASV or PORT.");
-               return 1;
+               return;
        }
 
 #if WANT_ASCII
        if ((c->rest_pos > 0) && (c->ascii_mode == 1)) {
                numeric(c, 500, "Cannot resume while in ASCII mode.");
-               return 1;
+               return;
        }
 #endif
 
@@ -636,7 +636,6 @@ int cmd_retr(struct conn * const c)
 #endif
                prepare_for_transfer(f);
        }
-       return 1;
 }
 
 #if WANT_UPLOAD
@@ -644,20 +643,18 @@ int cmd_retr(struct conn * const c)
  * cmd_stor(): Handles the STOR command (upload file). Pushes the
  *             work down to do_store(), below.
  */
-int cmd_stor(struct conn * const c)
+void cmd_stor(struct conn * const c)
 {
        do_store(c, 0);
-       return 1;
 }
 
 /*
  * cmd_appe(): Handles the APPE command (append to file). Pushes
  *             the work down to do_store(), below.
  */
-int cmd_appe(struct conn * const c)
+void cmd_appe(struct conn * const c)
 {
        do_store(c, 1);
-       return 1;
 }
 
 /*
@@ -714,22 +711,21 @@ void do_store(struct conn * const c, const int append)
  *             size of all the files in the directory, rather how
  *             much space the directory inode uses.
  */
-int cmd_size(struct conn * const c)
+void cmd_size(struct conn * const c)
 {
 #if WANT_ASCII
        if (c->ascii_mode) {
                numeric(c, 550, "SIZE not available in ASCII mode.");
-               return 1;
+               return;
        }
 #endif
        {
                const char * const fname = translate_path(c, c->recv_buf);
                struct stat buf;
        
-               TRAP_ERROR(fname == NULL || lstat(fname, &buf) == -1, 550, return 1);
+               TRAP_ERROR(fname == NULL || lstat(fname, &buf) == -1, 550, return);
        
                numeric(c, 213, "%lu", (unsigned long)(buf.st_size));
-               return 1;
        }
 }
 
@@ -738,86 +734,81 @@ int cmd_size(struct conn * const c)
  *             date/time of a file. See the comments on cmd_size(),
  *             above.
  */
-int cmd_mdtm(struct conn * const c)
+void cmd_mdtm(struct conn * const c)
 {
        const char * const fname = translate_path(c, c->recv_buf);
        struct stat buf;
        struct tm *m;
 
-       TRAP_ERROR(fname == NULL || lstat(fname, &buf) == -1, 550, return 1);
+       TRAP_ERROR(fname == NULL || lstat(fname, &buf) == -1, 550, return);
 
        m = gmtime(&(buf.st_mtime));    /* at least wu-ftpd does it in GMT */
        numeric(c, 213, "%u%02u%02u%02u%02u%02u", m->tm_year + 1900,
                m->tm_mon + 1, m->tm_mday, m->tm_hour, m->tm_min, m->tm_sec);
-       return 1;
 }
 
 /*
  * cmd_abor(): Handle the ABOR command (abort a file transfer). This should
  *             be clean enough, but isn't tested extensively.
  */
-int cmd_abor(struct conn * const c)
+void cmd_abor(struct conn * const c)
 {
        if (c->transfer != NULL) {
                numeric(c, 426, "File transfer aborted.");
                destroy_ftran(c->transfer);
        }
        numeric(c, 226, "ABOR command processed OK.");
-       return 1;
 }
 
 /*
  * cmd_dele(): Handle the DELE command (delete a file).
  */
-int cmd_dele(struct conn * const c)
+void cmd_dele(struct conn * const c)
 {
        const char * const fname = translate_path(c, c->recv_buf);
        
-       TRAP_ERROR(fname == NULL || unlink(fname) == -1, 550, return 1);
+       TRAP_ERROR(fname == NULL || unlink(fname) == -1, 550, return);
        numeric(c, 250, "File deleted OK.");
-       return 1;
 }
 
 /*
  * cmd_rnfr(): Handle the RNFR command (take a filename to rename from).
  */
-int cmd_rnfr(struct conn * const c)
+void cmd_rnfr(struct conn * const c)
 {
        const char * const fname = translate_path(c, c->recv_buf);
        char cwd[256];
        struct stat buf;
 
        c->rename_from[0] = '\0';
-       if (fname == NULL) return 1;
+       if (fname == NULL) return;
        
        getcwd(cwd, 256);
        snprintf(c->rename_from, 256, "%s/%s", cwd, fname);
 
        /* Just check that the file exists. */
-       TRAP_ERROR(lstat(c->rename_from, &buf) == -1, 550, c->rename_from[0] = '\0'; return 1);
+       TRAP_ERROR(lstat(c->rename_from, &buf) == -1, 550, c->rename_from[0] = '\0'; return);
 
        numeric(c, 350, "File exists, send RNTO.");
-       return 1;
 }
 
 /*
  * cmd_rnto(): Handle the RNTO command (do the actual renaming).
  */
-int cmd_rnto(struct conn * const c)
+void cmd_rnto(struct conn * const c)
 {
        const char * const fname = translate_path(c, c->recv_buf);
 
-       if (fname == NULL) return 1;
+       if (fname == NULL) return;
        if (c->rename_from[0] == '\0') {
                numeric(c, 503, "Please send RNFR first.");
-               return 1;
+               return;
        }
 
-       TRAP_ERROR(rename(c->rename_from, fname) == -1, 550, c->rename_from[0] = '\0'; return 1);
+       TRAP_ERROR(rename(c->rename_from, fname) == -1, 550, c->rename_from[0] = '\0'; return);
        c->rename_from[0] = '\0';
 
        numeric(c, 250, "File renamed successfully.");
-       return 1;
 }
 
 /*
@@ -834,18 +825,20 @@ int cmd_rnto(struct conn * const c)
  *             easy :-) (This code isn't quite easy to understand, because
  *             temp2 is used twice, in two different roles.)
  */
-int cmd_mkd(struct conn * const c)
+void cmd_mkd(struct conn * const c)
 {
        const char * const fname = translate_path(c, c->recv_buf);
        char temp[512], temp2[1024], *cdir;
        int i, j;
 
-       TRAP_ERROR(fname == NULL || mkdir(fname, 0755) == -1, 550, return 1);
+       TRAP_ERROR(fname == NULL || mkdir(fname, 0755) == -1, 550, return);
 
        chdir(fname);
        getcwd(temp2, 512);
        cdir = do_pwd(c, temp, temp2);
 
+       if (do_pwd == NULL) return;
+       
        /* double the quotes in the output */ 
        for (i = 0, j = 0; i <= strlen(cdir); i++, j++) {
                temp2[j] = cdir[i];
@@ -854,20 +847,18 @@ int cmd_mkd(struct conn * const c)
                }
        }
        numeric(c, 257, "\"%s\" created.", temp2);
-       return 1;
 }
 
 /*
  * cmd_rmd():  Handle the RMD/XRMD command. Works just like DELE, only for
  *             directories.
  */
-int cmd_rmd(struct conn * const c)
+void cmd_rmd(struct conn * const c)
 {
        const char * const fname = translate_path(c, c->recv_buf);
 
-       TRAP_ERROR(fname == NULL || rmdir(fname) == -1, 550, return 1);
+       TRAP_ERROR(fname == NULL || rmdir(fname) == -1, 550, return);
        numeric(c, 250, "Directory deleted.");
-       return 1;
 }
 
 /*
@@ -881,10 +872,9 @@ int cmd_rmd(struct conn * const c)
  *             to the full-screen mode, but close to no FTP clients send this
  *             command, and it would touch too much code.
  */
-int cmd_allo(struct conn * const c)
+void cmd_allo(struct conn * const c)
 {
        numeric(c, 202, "No storage allocation necessary.");
-       return 1;
 }
 
 /*
@@ -901,7 +891,7 @@ char conn_state[5][27] = {
        "Waiting for e-mail address",
        "Waiting for password",
        "Logged in",
-       "Waiting for password",         /* actually non-existant user */
+       "Logged in",            /* non-anonymous */
 };
 
 char ftran_state[6][42] = {
@@ -914,7 +904,7 @@ char ftran_state[6][42] = {
 };
 #endif
 
-int cmd_stat(struct conn * const c)
+void cmd_stat(struct conn * const c)
 { 
 #if WANT_STAT
        char buf[1024];
@@ -943,13 +933,11 @@ int cmd_stat(struct conn * const c)
 
        err = send(c->sock, buf, i, 0);
                if (err == -1 && errno == EPIPE) {
-                       destroy_conn(c);
-               return 0;
+               this->free_me = 1;
        }
 #else
        numeric(c, 502, "STAT command disabled for security reasons.");
 #endif
-       return 1;
 }
 
 #if HAVE_MMAP
@@ -1099,7 +1087,7 @@ int long_listing(char * const retbuf, const char * const pathname, const int do_
  *             long listing (of type `ls -l'). The listing work is
  *             done by do_listing(), below.
  */
-int cmd_list(struct conn * const c)
+void cmd_list(struct conn * const c)
 {
        struct list_options lo;
 
@@ -1108,7 +1096,6 @@ int cmd_list(struct conn * const c)
        lo.classify = 0;
 
        do_listing(c, &lo);
-       return 1;
 }
 
 /*
@@ -1118,7 +1105,7 @@ int cmd_list(struct conn * const c)
  *             FTP clients don't have a clue about what they send out). 
  *             The listing work is done by do_listing(), below.
  */    
-int cmd_nlst(struct conn * const c)
+void cmd_nlst(struct conn * const c)
 {
        struct list_options lo;
 
@@ -1127,7 +1114,6 @@ int cmd_nlst(struct conn * const c)
        lo.classify = 0;
 
        do_listing(c, &lo);
-       return 1;
 }
 
 /*
@@ -1416,25 +1402,23 @@ int list_core(struct conn * const c, const char * const pathname,
  * cmd_noop(): Handles the NOOP command. Does nothing, doesn't even
  *             reset the timeout.
  */
-int cmd_noop(struct conn * const c)
+void cmd_noop(struct conn * const c)
 {
        numeric(c, 200, "NOOP command successful.");
-       return 1;
 }
 
 /*
  * cmd_syst(): Handles the SYST command. Returns the system identification.
  */
-int cmd_syst(struct conn * const c)
+void cmd_syst(struct conn * const c)
 {
        numeric(c, 215, "UNIX Type: L%u", NBBY);
-       return 1;
 }
 
 /*
  * cmd_type(): Handles the TYPE command.
  */
-int cmd_type(struct conn * const c)
+void cmd_type(struct conn * const c)
 {
 #if WANT_ASCII
        c->recv_buf[0] &= (255-32);     /* convert to upper case */
@@ -1450,13 +1434,12 @@ int cmd_type(struct conn * const c)
 #else
        numeric(c, 200, "TYPE ignored (always I)");
 #endif
-       return 1;
 }
 
 /*
  * cmd_mode(): Handles the MODE command. Only stream mode is supported.
  */
-int cmd_mode(struct conn * const c)
+void cmd_mode(struct conn * const c)
 {
        c->recv_buf[0] &= (255-32);     /* convert to upper case */
        if (c->recv_buf[0] == 'S') {
@@ -1464,13 +1447,12 @@ int cmd_mode(struct conn * const c)
        } else {
                numeric(c, 504, "Unknown mode.");
        }
-       return 1;
 }
 
 /*
  * cmd_stru(): Handles the STRU command. Only file mode is supported.
  */
-int cmd_stru(struct conn * const c)
+void cmd_stru(struct conn * const c)
 {
        c->recv_buf[0] &= (255-32);     /* convert to upper case */
        if (c->recv_buf[0] == 'F') {
@@ -1478,7 +1460,6 @@ int cmd_stru(struct conn * const c)
        } else {
                numeric(c, 504, "Unknown structure.");
        }
-       return 1;
 }
 
 /*
@@ -1498,21 +1479,19 @@ int cmd_stru(struct conn * const c)
  *             like an error, since the error code is intended for helpful
  *             messages? :-)
  */
-int cmd_help(struct conn * const c)
+void cmd_help(struct conn * const c)
 {
        numeric(c, 414, "Sorry, no detailed help; use standard FTP commands.");
-       return 1;
 }
 
 /*
  * cmd_quit(): Handles the QUIT command, which shuts down the control
  *             and data sockets.
  */
-int cmd_quit(struct conn * const c)
+void cmd_quit(struct conn * const c)
 {
        numeric(c, 221, "Have a nice day!");
-       destroy_conn(c);
-       return 0;
+       c->free_me = 1;
 }
 
 /*
@@ -1521,7 +1500,7 @@ int cmd_quit(struct conn * const c)
  *             copied directly from alloc_new_conn() -- perhaps we should
  *             modularize this?
  */
-int cmd_rein(struct conn * const c)
+void cmd_rein(struct conn * const c)
 {
        destroy_ftran(c->transfer);
        c->buf_len = c->auth = c->rest_pos = 0;
@@ -1536,8 +1515,6 @@ int cmd_rein(struct conn * const c)
 
        time(&(c->last_transfer));
        numeric(c, 220, "BetaFTPD " VERSION " ready.");
-
-       return 1;
 }
 
 #if DOING_PROFILING
@@ -1601,9 +1578,11 @@ void parse_command(struct conn *c)
 
 #if !WANT_NONROOT
                                if (h->do_setuid) {
+                                       setegid(c->gid);
                                        seteuid(c->uid);
                                } else {
-                                       seteuid(0);
+                                       seteuid(getuid());
+                                       setegid(getgid());
                                }
 #endif
 
@@ -1616,12 +1595,24 @@ void parse_command(struct conn *c)
 
                                schar = c->recv_buf[cmlen];
                                c->recv_buf[cmlen] = 0;
-
-                               /* result of zero means the connection is freed */
-                               if (h->callback(c)) {
+                               
+                               message_buf[0] = '\0';
+                               h->callback(c);
+                               if (message_buf[0] != '\0') {
+                                       /* send any feedback we might have */
+                                       int err = send(c->sock, message_buf, strlen(message_buf), 0);
+                                       if (err == -1 && errno == EPIPE) {
+                                               c->free_me = 1;
+                                       }
+                               }       
+                               
+                               if (!c->free_me) {
                                        c->recv_buf[cmlen] = schar;
 #if !WANT_NONROOT
-                                       if (h->do_setuid) seteuid(getuid());
+                                       if (h->do_setuid) {
+                                               seteuid(getuid());
+                                               setegid(getgid());
+                                       }
 #endif
                                        remove_bytes(c, cmlen);
                                }
@@ -1630,8 +1621,12 @@ void parse_command(struct conn *c)
                }
        } while ((++h)->callback != NULL);
 
-       numeric(c, 500, "Sorry, no such command.");
        remove_bytes(c, cmlen); 
+       {
+               char error[] = "500 Sorry, no such command.\r\n";
+               if (send(c->sock, error, strlen(error), 0) == -1 && errno == EPIPE)
+                       destroy_conn(c);
+       }
 }
 
 /*
@@ -1832,7 +1827,7 @@ int prepare_for_listing(struct conn * const c, char ** const ptr,
                        case 'F':
                                lo->classify = 1;
                                break;
-                       case ' ':
+                       case '\0':
                                fptr = optr + 1;
                                *(optr--) = 0;
                                break;
@@ -1843,7 +1838,7 @@ int prepare_for_listing(struct conn * const c, char ** const ptr,
        } else {
                fptr = c->recv_buf;
        }
-       
+
        /* then we chdir to the dir in fptr (if any) */
        tmp = fptr ? strrchr(fptr, '/') : NULL;
        if (tmp != NULL) {
@@ -1856,7 +1851,16 @@ int prepare_for_listing(struct conn * const c, char ** const ptr,
        }
 
        /* if no argument, choose all files */
-       if (fptr == NULL || fptr[0] == 0) fptr = "*";
+       if (fptr == NULL || fptr[0] == 0) {
+               fptr = "*";
+       } else {
+               /* we need to check if the last part is a directory (no -d switch) */
+               struct stat buf;
+               if (stat(fptr, &buf) == 0 && S_ISDIR(buf.st_mode)) {
+                       TRAP_ERROR(chdir(fptr) == -1, 550, return -1);
+                       fptr = "*";
+               }
+       }
        *ptr = fptr;
 
 #if WANT_NONROOT