/* * ftp.c * * FTP accesing in RFC959 */ #include "headers.h" #include "depend.h" #include "printf.h" #include "kctype.h" #include "string.h" #include "malloc.h" #include "time.h" #include "sysemu.h" #include "pathname.h" #include "termio.h" #include "parse.h" #include "lsparse.h" #include "socket.h" #include "auth.h" #include "urldisk.h" #ifndef NOFTPH #include #endif #define FTP_QUIT 0 #define FTP_USER 1 #define FTP_PASS 2 #define FTP_CWD 3 #define FTP_PWD 4 #define FTP_TYPE 5 #define FTP_LIST 6 #define FTP_NLST 7 #define FTP_PORT 8 #define FTP_PASV 9 #define FTP_MDTM 10 #define FTP_CHMOD 11 #define FTP_DELE 12 #define FTP_RNFR 13 #define FTP_RNTO 14 #define FTP_RETR 15 #define FTP_STOR 16 #define FTP_MKD 17 #define FTP_RMD 18 #define FTP_FEAT 19 #define FTP_ABOR 20 #define FTP_SIZE 21 #define FTP_ASCII "A" #define FTP_IMAGE "I" #ifndef PRELIM #define PRELIM 1 #endif #ifndef COMPLETE #define COMPLETE 2 #endif #ifndef CONTINUE #define CONTINUE 3 #endif #ifndef TRANSIENT #define TRANSIENT 4 #endif #ifndef ERROR #define ERROR 5 #endif typedef struct _ftpcmd_t { int id; CONST char *cmd[2]; int argc; int flags; } ftpcmd_t; #define FFL_INT 0001 #define FFL_COMMA 0002 #define FFL_LIST 0004 #ifdef DEP_FTPPATH static VOID NEAR vftplog __P_((CONST char *, va_list)); static VOID NEAR ftplog __P_((CONST char *, ...)); static char *NEAR ftprecv __P_((XFILE *)); static int NEAR vftpsend __P_((XFILE *, int, CONST char *, va_list)); static int NEAR ftppasv __P_((int)); static int NEAR ftpdata __P_((int)); static int NEAR sendrequest __P_((int, int, CONST char *)); static int NEAR ftpopenport __P_((int, int, CONST char *, CONST char *)); static char *ftpfgets __P_((VOID_P)); static int NEAR _ftprecvlist __P_((int, CONST char *, namelist **)); static int NEAR recvpwd __P_((int, char *, ALLOC_T)); static int NEAR ftpfeat __P_((int)); static int NEAR ftpmdtm __P_((int, CONST char *, namelist *, int, int)); static int NEAR ftpsize __P_((int, CONST char *, namelist *, int, int)); char *ftpaddress = NULL; char *ftpproxy = NULL; char *ftplogfile = NULL; static char *form_ftp[] = { "%a %l %u %g %s %m %d %{yt} %*f", "%a %l %u %g %B, %b %m %d %{yt} %*f", "%a %l %u %s %m %d %{yt} %*f", "%a %l %u %B, %b %m %d %{yt} %*f", "%m-%d-%y %t %{s//} %*f", "%s %/DIR/ %m-%d-%y %t %*f", "%s %m-%d-%y %t %*f", NULL }; static char *form_nlst[] = { "%*f", NULL }; static char *ign_ftp[] = { "total *", NULL }; static lsparse_t ftpformat = { NULL, NULL, form_ftp, ign_ftp, NULL, 0, 0, LF_NOTRAVERSE }; static CONST ftpcmd_t ftpcmdlist[] = { {FTP_QUIT, {"QUIT", NULL}, 0, 0}, {FTP_USER, {"USER", NULL}, 1, 0}, {FTP_PASS, {"PASS", NULL}, 1, 0}, {FTP_CWD, {"CWD", "XCWD"}, 1, 0}, {FTP_PWD, {"PWD", "XPWD"}, 0, 0}, {FTP_TYPE, {"TYPE", NULL}, 1, 0}, {FTP_LIST, {"LIST -la", "LIST"}, 1, FFL_LIST}, {FTP_NLST, {"NLST -a", "NLST"}, 1, FFL_LIST}, {FTP_PORT, {"PORT", NULL}, 6, FFL_INT | FFL_COMMA}, {FTP_PASV, {"PASV", NULL}, 0, 0}, {FTP_MDTM, {"MDTM", NULL}, 1, 0}, {FTP_CHMOD, {"SITE CHMOD", NULL}, 2, 0}, {FTP_DELE, {"DELE", NULL}, 1, 0}, {FTP_RNFR, {"RNFR", NULL}, 1, 0}, {FTP_RNTO, {"RNTO", NULL}, 1, 0}, {FTP_RETR, {"RETR", NULL}, 1, 0}, {FTP_STOR, {"STOR", NULL}, 1, 0}, {FTP_MKD, {"MKD", "XMKD"}, 1, 0}, {FTP_RMD, {"RMD", "XRMD"}, 1, 0}, {FTP_FEAT, {"FEAT", NULL}, 0, 0}, {FTP_ABOR, {"ABOR", NULL}, 0, 0}, {FTP_SIZE, {"SIZE", NULL}, 1, 0}, }; #define FTPCMDLISTSIZ arraysize(ftpcmdlist) static VOID NEAR vftplog(fmt, args) CONST char *fmt; va_list args; { XFILE *fp; if (!ftplogfile || !isrootdir(ftplogfile)) return; if (!(fp = Xfopen(ftplogfile, "a"))) return; if (logheader) { Xfputs(logheader, fp); Xfree(logheader); logheader = NULL; } VOID_C Xvfprintf(fp, fmt, args); VOID_C Xfclose(fp); } #ifdef USESTDARGH /*VARARGS1*/ static VOID NEAR ftplog(CONST char *fmt, ...) #else /*VARARGS1*/ static VOID NEAR ftplog(fmt, va_alist) CONST char *fmt; va_dcl #endif { va_list args; VA_START(args, fmt); vftplog(fmt, args); va_end(args); } static char *NEAR ftprecv(fp) XFILE *fp; { char *buf; buf = Xfgets(fp); if (!buf) return(NULL); ftplog("<-- \"%s\"\n", buf); return(buf); } static int NEAR vftpsend(fp, cmd, fmt, args) XFILE *fp; int cmd; CONST char *fmt; va_list args; { u_char oob[3]; char buf[URLMAXCMDLINE + 1]; int n; n = Xsnprintf(buf, sizeof(buf), "--> \"%s\"\n", fmt); if (n < 0) /*EMPTY*/; else if (cmd == FTP_PASS) ftplog(buf, "????"); else vftplog(buf, args); if (cmd == FTP_ABOR) { oob[0] = TELNET_IAC; oob[1] = TELNET_IP; oob[2] = TELNET_IAC; n = socksendoob(Xfileno(fp), oob, sizeof(oob)); if (n >= 0) VOID_C Xfputc(TELNET_DM, fp); } n = Xvfprintf(fp, fmt, args); if (n >= 0) n = fputnl(fp); return(n); } int ftpgetreply(fp, sp) XFILE *fp; char **sp; { char *cp, *buf; ALLOC_T len, size; int i, n, code; if (!(buf = ftprecv(fp))) return(-1); for (i = n = 0; i < 3; i++) { if (!Xisdigit(buf[i])) break; n *= 10; n += buf[i] - '0'; } code = n; if (i < 3) { Xfree(buf); return(seterrno(EINVAL)); } if (buf[3] == '-') { size = strlen(buf); for (;;) { if (!(cp = ftprecv(fp))) { Xfree(buf); return(-1); } for (i = n = 0; i < 3; i++) { if (!Xisdigit(cp[i])) break; n *= 10; n += cp[i] - '0'; } if (i < 3 || cp[3] != ' ') n = -1; len = strlen(cp); buf = Xrealloc(buf, size + 1 + len + 1); buf[size++] = '\n'; memcpy(&(buf[size]), cp, len + 1); size += len; Xfree(cp); if (n == code) break; } } if (sp) *sp = buf; else Xfree(buf); return(code); } int ftpseterrno(n) int n; { if (n < 0) return(-1); switch (n / 100) { case COMPLETE: n = 0; break; case PRELIM: case CONTINUE: n = seterrno(EACCES); break; case TRANSIENT: n = seterrno(EIO); break; default: switch (n) { case 550: n = seterrno(ENOENT); break; default: n = seterrno(EINVAL); break; } break; } return(n); } int vftpcommand(uh, sp, cmd, args) int uh; char **sp; int cmd; va_list args; { char *cp, buf[URLMAXCMDLINE + 1]; ALLOC_T size; int i, j, n, c, delim; if (sp) *sp = NULL; if (cmd < 0 || cmd >= FTPCMDLISTSIZ) return(seterrno(EINVAL)); n = 0; for (i = 0; i < 2; i++) { if (!(ftpcmdlist[cmd].cmd[i])) break; if (i || !(ftpcmdlist[cmd].flags & FFL_LIST)) /*EMPTY*/; else if (urlhostlist[uh].options & UOP_NOLISTOPT) continue; /* Some FTP proxy cannot allow option arguments */ if (urlhostlist[uh].flags & UFL_PROXIED) { cp = Xstrchr(ftpcmdlist[cmd].cmd[i], ' '); if (cp && *skipspace(++cp) == '-') continue; } cp = buf; size = sizeof(buf); n = Xsnprintf(cp, size, "%s", ftpcmdlist[cmd].cmd[i]); if (n < 0) break; delim = ' '; c = (ftpcmdlist[cmd].flags & FFL_INT) ? 'd' : 's'; for (j = 0; j < ftpcmdlist[cmd].argc; j++) { cp += n; size -= n; n = Xsnprintf(cp, size, "%c%%%c", delim, c); if (n < 0) break; if (ftpcmdlist[cmd].flags & FFL_COMMA) delim = ','; } if (n < 0) break; n = vftpsend(urlhostlist[uh].fp, cmd, buf, args); if (n < 0) break; if (cmd == FTP_QUIT) Xsettimeout(urlhostlist[uh].fp, URLENDTIMEOUT); n = urlgetreply(uh, sp); Xsettimeout(urlhostlist[uh].fp, urltimeout); if (n == 500) continue; if (n != 421 || cmd == FTP_USER || cmd == FTP_PASS) break; if (urlreconnect(uh) < 0) break; if (ftplogin(uh) < 0) { safefclose(urlhostlist[uh].fp); urlhostlist[uh].fp = NULL; break; } i--; } return(n); } int ftpquit(uh) int uh; { if (urlhostlist[uh].flags & UFL_LOCKED) return(0); return(urlcommand(uh, NULL, FTP_QUIT)); } int ftpabort(uh) int uh; { return(urlcommand(uh, NULL, FTP_ABOR)); } int ftplogin(uh) int uh; { CONST char *pass; char buf[URLMAXCMDLINE + 1]; int n, anon, flags; flags = (UGP_USER | UGP_ANON); if (urlhostlist[uh].flags & UFL_PROXIED) { flags |= UGP_HOST; urlhostlist[uh].options |= UOP_NOFEAT; } if (urlgenpath(uh, buf, sizeof(buf), flags) < 0) return(-1); n = urlcommand(uh, NULL, FTP_USER, buf); if (n / 100 == CONTINUE) { anon = 0; if ((pass = urlhostlist[uh].host.pass)) /*EMPTY*/; else if (urlhostlist[uh].host.user) pass = nullstr; else { anon++; pass = (ftpaddress && *ftpaddress) ? ftpaddress : FTPANONPASS; } n = urlcommand(uh, NULL, FTP_PASS, pass); if (n == 530) { if (anon) VOID_C Xfprintf(Xstderr, "%s: Invalid address.\r\n", pass); else VOID_C Xfprintf(Xstderr, "%s: Login incorrect.\r\n", buf); Xfree(urlhostlist[uh].host.pass); urlhostlist[uh].host.pass = NULL; } } if (ftpseterrno(n) < 0) return(-1); authentry(&(urlhostlist[uh].host), urlhostlist[uh].type); VOID_C ftpfeat(uh); return(0); } static int NEAR ftppasv(uh) int uh; { u_char addr[4], port[2]; char *cp, *buf, host[4 * 4]; int n; n = urlcommand(uh, &buf, FTP_PASV); if (ftpseterrno(n) < 0) { Xfree(buf); return(-1); } if ((cp = Xstrchr(buf, '('))) cp = Xsscanf(cp, "(%Cu,%Cu,%Cu,%Cu,%Cu,%Cu)", &(addr[0]), &(addr[1]), &(addr[2]), &(addr[3]), &(port[0]), &(port[1])); Xfree(buf); if (!cp) return(seterrno(EINVAL)); n = Xsnprintf(host, sizeof(host), "%u.%u.%u.%u", addr[0], addr[1], addr[2], addr[3]); if (n < 0) return(seterrno(EINVAL)); n = (port[0] << 8 | port[1]); return(sockconnect(host, n, urltimeout, SCK_THROUGHPUT)); } static int NEAR ftpdata(uh) int uh; { u_char addr[4]; char *cp, host[SCK_ADDRSIZE + 1]; int n, s, port, opt; if (!(urlhostlist[uh].options & UOP_NOPASV)) return(ftppasv(uh)); s = Xfileno(urlhostlist[uh].fp); if (getsockinfo(s, host, sizeof(host), &port, 0) < 0) return(-1); if (urlhostlist[uh].options & UOP_NOPORT) opt = (SCK_LOWDELAY | SCK_REUSEADDR); else { opt = SCK_LOWDELAY; port = 0; } if ((s = sockbind(host, port, opt)) < 0) return(-1); if (urlhostlist[uh].options & UOP_NOPORT) return(s); if (getsockinfo(s, host, sizeof(host), &port, 0) < 0) { safeclose(s); return(-1); } cp = Xsscanf(host, "%Cu.%Cu.%Cu.%Cu%$", &(addr[0]), &(addr[1]), &(addr[2]), &(addr[3])); if (!cp) return(seterrno(EINVAL)); n = urlcommand(uh, NULL, FTP_PORT, addr[0], addr[1], addr[2], addr[3], (port >> 8) & 0xff, port & 0xff); if (ftpseterrno(n) < 0) { safeclose(s); return(-1); } return(s); } static int NEAR sendrequest(uh, cmd, path) int uh, cmd; CONST char *path; { int n; if ((n = urlcommand(uh, NULL, cmd, path)) < 0) return(-1); if (n / 100 == PRELIM) return(1); if (n != 425) return(seterrno(EINVAL)); if ((urlhostlist[uh].flags & UFL_RETRYPORT) && (urlhostlist[uh].flags & UFL_RETRYPASV)) return(seterrno(EINVAL)); if (!(urlhostlist[uh].options & UOP_NOPASV) || (urlhostlist[uh].flags & UFL_RETRYPORT)) { urlhostlist[uh].flags |= UFL_RETRYPASV; urlhostlist[uh].options ^= UOP_NOPASV; } else { urlhostlist[uh].flags |= UFL_RETRYPORT; urlhostlist[uh].options ^= UOP_NOPORT; } return(0); } static int NEAR ftpopenport(uh, cmd, path, type) int uh, cmd; CONST char *path, *type; { int n, fd; n = urlcommand(uh, NULL, FTP_TYPE, type); if (ftpseterrno(n) < 0) return(-1); for (;;) { if ((fd = ftpdata(uh)) < 0) return(-1); if ((n = sendrequest(uh, cmd, path)) > 0) break; if (n < 0) { safeclose(fd); return(-1); } } if (urlhostlist[uh].options & UOP_NOPASV) fd = sockaccept(fd, SCK_THROUGHPUT); return(fd); } static char *ftpfgets(vp) VOID_P vp; { char *buf; buf = Xfgets((XFILE *)vp); if (!buf) return(NULL); ftplog(" \"%s\"\n", buf); return(buf); } static int NEAR _ftprecvlist(uh, path, listp) int uh; CONST char *path; namelist **listp; { XFILE *fp; int n, fd, cmd, max; if (urlhostlist[uh].options & UOP_USENLST) { cmd = FTP_NLST; ftpformat.format = form_nlst; ftpformat.flags |= LF_BASENAME; } else { cmd = FTP_LIST; ftpformat.format = form_ftp; ftpformat.flags &= ~LF_BASENAME; } if ((fd = ftpopenport(uh, cmd, path, FTP_ASCII)) < 0) return(-1); if (!(fp = Xfdopen(fd, "r"))) { safeclose(fd); return(-1); } Xsettimeout(fp, urltimeout); Xsetflags(fp, XF_CRNL | XF_NONBLOCK); *listp = NULL; ftplog("<-- (data)\n"); max = lsparse(fp, &ftpformat, listp, ftpfgets); safefclose(fp); if (max < 0) return(-1); n = urlgetreply(uh, NULL); if (ftpseterrno(n) < 0) { freelist(*listp, max); return(-1); } return(max); } int ftprecvlist(uh, path, listp) int uh; CONST char *path; namelist **listp; { int n; n = _ftprecvlist(uh, path, listp); if (n < 0 || (urlhostlist[uh].flags & UFL_FIXLISTOPT)) return(n); if (n > 1) { urlhostlist[uh].flags |= UFL_FIXLISTOPT; return(n); } urlhostlist[uh].options ^= UOP_NOLISTOPT; n = _ftprecvlist(uh, path, listp); urlhostlist[uh].options ^= UOP_NOLISTOPT; if (n > 1) { urlhostlist[uh].options ^= UOP_NOLISTOPT; urlhostlist[uh].flags |= UFL_FIXLISTOPT; } return(n); } static int NEAR recvpwd(uh, path, size) int uh; char *path; ALLOC_T size; { char *buf; int i, j, n, quote; if ((n = urlcommand(uh, &buf, FTP_PWD)) < 0) return(-1); if (n != 257) { Xfree(buf); return(seterrno(EACCES)); } for (i = 4; Xisblank(buf[i]); i++) /*EMPTY*/; quote = '\0'; for (j = 0; buf[i]; i++) { if (buf[i] == quote) { if (buf[i + 1] != quote) { quote = '\0'; continue; } i++; } else if (quote) /*EMPTY*/; else if (Xisblank(buf[i])) break; else if (buf[i] == '"') { quote = buf[i]; continue; } if (j >= size - 1) break; path[j++] = buf[i]; } path[j] = '\0'; Xfree(buf); return(0); } static int NEAR ftpfeat(uh) int uh; { char *cp, *next, *buf; int n, mdtm, size; if (urlhostlist[uh].options & UOP_NOFEAT) return(0); n = urlcommand(uh, &buf, FTP_FEAT); if (ftpseterrno(n) < 0) { if (n == 500 || n == 502) urlhostlist[uh].options |= UOP_NOFEAT; Xfree(buf); return(-1); } next = NULL; mdtm = size = 0; for (cp = buf; cp && *cp; cp = next) { while (Xisblank(*cp)) cp++; if (!(next = Xstrchr(cp, '\n'))) n = strlen(cp); else { n = next - cp; *(next++) = '\0'; } if (!Xstrcasecmp(cp, "MDTM")) mdtm++; if (!Xstrcasecmp(cp, "SIZE")) size++; } if (!mdtm) urlhostlist[uh].options |= UOP_NOMDTM; if (!size) urlhostlist[uh].options |= UOP_NOSIZE; Xfree(buf); return(0); } static int NEAR ftpmdtm(uh, path, namep, st, ent) int uh; CONST char *path; namelist *namep; int st, ent; { namelist *tmp; struct tm tm; char *cp, *buf; time_t t; int n, year, mon, day, hour, min, sec; if (urlhostlist[uh].options & UOP_NOMDTM) return(0); if (ismark(namep)) return(0); tmp = NULL; if (st >= 0 && st < maxurlstat && ent >= 0 && ent < urlstatlist[st].max) tmp = &(urlstatlist[st].list[ent]); n = urlcommand(uh, &buf, FTP_MDTM, path); if (n >= 0 && tmp) tmp -> tmpflags |= F_ISMRK; if (ftpseterrno(n) < 0) { Xfree(buf); if (n == 500 || n == 502) urlhostlist[uh].options |= UOP_NOMDTM; if (n == 550) { if (!isdir(namep)) todirlist(namep, (u_int)-1); if (tmp && !isdir(tmp)) todirlist(tmp, (u_int)-1); return(0); } return(-1); } if ((cp = Xstrchr(buf, ' '))) { cp = skipspace(&(cp[1])); cp = Xsscanf(cp, "%04u%02u%02u%02u%02u%02u", &year, &mon, &day, &hour, &min, &sec); } Xfree(buf); if (!cp) return(seterrno(EINVAL)); tm.tm_year = year - 1900; tm.tm_mon = mon - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = min; tm.tm_sec = sec; t = Xtimegm(&tm); if (t == (time_t)-1) return(seterrno(EINVAL)); namep -> st_mtim = t; if (tmp) tmp -> st_mtim = t; return(0); } static int NEAR ftpsize(uh, path, namep, st, ent) int uh; CONST char *path; namelist *namep; int st, ent; { namelist *tmp; char *cp, *buf; off_t size; int n; if (urlhostlist[uh].options & UOP_NOSIZE) return(0); if (wasmark(namep)) return(0); tmp = NULL; if (st >= 0 && st < maxurlstat && ent >= 0 && ent < urlstatlist[st].max) tmp = &(urlstatlist[st].list[ent]); n = urlcommand(uh, &buf, FTP_SIZE, path); if (n >= 0 && tmp) tmp -> tmpflags |= F_WSMRK; if (ftpseterrno(n) < 0) { Xfree(buf); if (n == 500 || n == 502) urlhostlist[uh].options |= UOP_NOSIZE; if (n == 550) { if (!isdir(namep)) todirlist(namep, (u_int)-1); if (tmp && !isdir(tmp)) todirlist(tmp, (u_int)-1); return(0); } return(-1); } if ((cp = Xstrchr(buf, ' '))) { cp = skipspace(&(cp[1])); cp = Xsscanf(cp, "%qu", &size); } Xfree(buf); if (!cp) return(seterrno(EINVAL)); namep -> st_size = size; if (tmp) tmp -> st_size = size; return(0); } int ftprecvstatus(uh, path, namep, n, ent) int uh; CONST char *path; namelist *namep; int n, ent; { if (ftpmdtm(uh, path, namep, n, ent) < 0) return(-1); if (!(urlhostlist[uh].options & UOP_USENLST)) /*EMPTY*/; else if (ftpsize(uh, path, namep, n, ent) < 0) return(-1); return(0); } int ftpchdir(uh, path) int uh; CONST char *path; { return(ftpseterrno(urlcommand(uh, NULL, FTP_CWD, path))); } char *ftpgetcwd(uh, path, size) int uh; char *path; ALLOC_T size; { char *cp; int n; cp = path; n = urlgenpath(uh, cp, size, UGP_SCHEME | UGP_USER | UGP_HOST); if (n < 0) return(NULL); cp += n; size -= n; if (recvpwd(uh, cp, size) < 0) return(NULL); return(path); } int ftpchmod(uh, path, mode) int uh; CONST char *path; int mode; { char buf[MAXLONGWIDTH + 1]; int n; mode &= 0777; if (Xsnprintf(buf, sizeof(buf), "%03o", mode) < 0) return(-1); n = urlcommand(uh, NULL, FTP_CHMOD, buf, path); return(ftpseterrno(n)); } int ftpunlink(uh, path) int uh; CONST char *path; { return(ftpseterrno(urlcommand(uh, NULL, FTP_DELE, path))); } int ftprename(uh, from, to) int uh; CONST char *from, *to; { int n; n = urlcommand(uh, NULL, FTP_RNFR, from); if (n / 100 != CONTINUE) { if (n >= 0) errno = ENOENT; return(-1); } return(ftpseterrno(urlcommand(uh, NULL, FTP_RNTO, to))); } int ftpopen(uh, path, flags) int uh; CONST char *path; int flags; { int cmd; switch (flags & O_ACCMODE) { case O_RDONLY: cmd = FTP_RETR; break; case O_WRONLY: cmd = FTP_STOR; break; default: return(seterrno(EACCES)); /*NOTREACHED*/ break; } return(ftpopenport(uh, cmd, path, FTP_IMAGE)); } /*ARGSUSED*/ int ftpclose(uh, fd) int uh, fd; { return(ftpseterrno(urlgetreply(uh, NULL))); } /*ARGSUSED*/ int ftpfstat(uh, stp) int uh; struct stat *stp; { return(seterrno(ENOENT)); } /*ARGSUSED*/ int ftpread(uh, fd, buf, nbytes) int uh, fd; char *buf; int nbytes; { return(read(fd, buf, nbytes)); } /*ARGSUSED*/ int ftpwrite(uh, fd, buf, nbytes) int uh, fd; CONST char *buf; int nbytes; { return(write(fd, buf, nbytes)); } int ftpmkdir(uh, path) int uh; CONST char *path; { return(ftpseterrno(urlcommand(uh, NULL, FTP_MKD, path))); } int ftprmdir(uh, path) int uh; CONST char *path; { return(ftpseterrno(urlcommand(uh, NULL, FTP_RMD, path))); } #endif /* DEP_FTPPATH */