X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=cmds.c;h=2e38823ab9d4716ae5c2f6aae5805e513c819fca;hb=36a3d95e46fd6b07fb8a2dfc30ec37c21b34b41a;hp=a2e047bc51eef0a54ffc3d8d46ea4f74c74108ba;hpb=1d1dd8f55a8b91e873db2f4255a1f1d9446e1bd0;p=betaftpd diff --git a/cmds.c b/cmds.c index a2e047b..2e38823 100644 --- 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, @@ -21,14 +21,14 @@ #include #endif -#if HAVE_SYS_TYPES_H -#include -#endif - #if HAVE_STROPTS_H #include #endif +#if HAVE_SYS_TYPES_H +#include +#endif + #if HAVE_SYS_CONF_H #include #endif @@ -77,10 +77,6 @@ #include #endif -#if HAVE_PWD_H -#include -#endif - #if HAVE_GRP_H #include #endif @@ -89,10 +85,6 @@ #include #endif -#if HAVE_SYS_SOCKET_H -#include -#endif - #if HAVE_SYS_STAT_H #include #endif @@ -109,10 +101,6 @@ #include #endif -#if HAVE_NETINET_IN_H -#include -#endif - #if HAVE_SHADOW_H #include #endif @@ -161,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 */ @@ -275,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; @@ -290,7 +278,6 @@ int cmd_user(struct conn * const c) numeric(c, 331, "Password required for %s.", c->username); c->auth = 2; } - return 1; } /* @@ -301,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, @@ -321,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; } @@ -340,7 +328,7 @@ int cmd_pass(struct conn * const c) ) { c->auth = 0; } else { - c->auth = 3; + c->auth = 4; } } #endif /* !WANT_NONROOT */ @@ -356,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; } /* @@ -372,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; } /* @@ -385,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; @@ -394,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); @@ -413,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."); @@ -422,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 @@ -443,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; @@ -459,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); @@ -475,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)", @@ -496,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; } /* @@ -507,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; @@ -515,7 +502,6 @@ int cmd_pwd(struct conn * const c) if (cdir != NULL) { numeric(c, 257, "\"%s\" is current working directory.", cdir); } - return 1; } /* @@ -530,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; @@ -549,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; } /* @@ -565,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; } /* @@ -603,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; } /* @@ -617,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 @@ -652,7 +636,6 @@ int cmd_retr(struct conn * const c) #endif prepare_for_transfer(f); } - return 1; } #if WANT_UPLOAD @@ -660,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; } /* @@ -730,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; } } @@ -754,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; } /* @@ -850,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]; @@ -870,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; } /* @@ -897,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; } /* @@ -917,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] = { @@ -930,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]; @@ -959,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 @@ -1115,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; @@ -1124,7 +1096,6 @@ int cmd_list(struct conn * const c) lo.classify = 0; do_listing(c, &lo); - return 1; } /* @@ -1134,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; @@ -1143,7 +1114,6 @@ int cmd_nlst(struct conn * const c) lo.classify = 0; do_listing(c, &lo); - return 1; } /* @@ -1187,30 +1157,17 @@ void do_listing(struct conn * const c, struct list_options * const lo) #if WANT_DCACHE { - struct dcache *d = NULL, *next = first_dcache->next_dcache; - struct stat buf; - - if (stat(cwd, &buf) > -1) { - /* run through the linked list */ - while (next != NULL) { - d = next; - next = d->next_dcache; - - if (buf.st_mtime <= d->generated && - strcmp(d->dir_name, cwd) == 0 && - strcmp(d->pattern, ptr) == 0 && - memcmp(&(d->lo), lo, - sizeof(struct list_options)) == 0) { - d->use_count++; - f->dir_cache = d; - f->file_data = d->dir_data; - f->size = d->dir_size; - f->dir_listing = 1; - f->pos = 0; - prepare_for_transfer(f); - return; - } - } + struct dcache *d = find_dcache(cwd, ptr, lo); + if (d != NULL) { + d->use_count++; + f->dir_cache = d; + f->file_data = d->dir_data; + f->size = d->dir_size; + f->dir_listing = 1; + f->pos = 0; + + prepare_for_transfer(f); + return; } } #endif @@ -1445,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 */ @@ -1479,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') { @@ -1493,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') { @@ -1507,7 +1460,6 @@ int cmd_stru(struct conn * const c) } else { numeric(c, 504, "Unknown structure."); } - return 1; } /* @@ -1527,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; } /* @@ -1550,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; @@ -1565,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 @@ -1630,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 @@ -1645,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); } @@ -1659,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); + } } /* @@ -1861,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; @@ -1872,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) { @@ -1885,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