/* Copyright (C) 1999 Beau Kuiper This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "ftpd.h" #include "reply.h" #define LISTALL 1 #define LISTDIR 2 #define LISTRECURSE 4 int ftplist_parseflags(char *params) { int count, output = 0; for (count = 0; count < strlen(params); count++) { if (params[count] == 'a') output = output | LISTALL; if (params[count] == 'd') output = output | LISTDIR; if (params[count] == 'R') output = output | LISTRECURSE; if (params[count] == ' ') return(output); } return(output); } char *listmakeline(FTPSTATE *peer, char *longname, char *filename, struct stat *fileinfo, STRCACHE *uidcache, STRCACHE *gidcache, int year) { char *output = NULL; char *datastr; char permissions[11]; char date[20]; char size[40]; char *username; char *group; int links; /* make sure the stat did work */ if (fileinfo->st_nlink > 0) { char numuserstr[20]; char numgroupstr[20]; /* insert the permissions/ filetype */ if (peer->fakemode != -1) { int dirmode = (peer->fakemode & 0777) | S_IFDIR; dirmode |= (dirmode & 0444) >> 2; if (S_ISDIR(fileinfo->st_mode)) convertperms(permissions, dirmode); else convertperms(permissions, peer->fakemode | S_IFREG); } else convertperms(permissions, fileinfo->st_mode); permissions[10] = 0; /* Insert links */ links = MINIMUM(fileinfo->st_nlink, 999); /* Insert size */ if (S_ISCHR(fileinfo->st_mode) || S_ISBLK(fileinfo->st_mode)) { snprintf(size, 39, "%3d, %3d", (int)(fileinfo->st_rdev) >> 8, (int)(fileinfo->st_rdev) & 255); } else snprintf(size, 39, "%8s", offt_tostr(fileinfo->st_size)); /* Insert date */ datastr = ctime(&(fileinfo->st_mtime)); memcpy(date, datastr + 4, 12); if (memcmp(&year, datastr + 20, 4) != 0) { date[7] = ' '; memcpy(date + 8, datastr + 20, 4); } date[12] = 0; /* do owner */ if (peer->fakename) username = peer->fakename; else if ((username = strcache_check(uidcache, (int)fileinfo->st_uid)) == NULL) { username = get_passwdname(fileinfo->st_uid, peer->chroot); if (username == NULL) { sprintf(numuserstr, "%d", (int)fileinfo->st_uid); username = numuserstr; } strcache_add(uidcache, (int)fileinfo->st_uid, username); } username[8] = 0; /* do group */ if (peer->fakegroup) group = peer->fakegroup; else if ((group = strcache_check(gidcache, fileinfo->st_gid)) == NULL) { group = get_groupname(fileinfo->st_gid, peer->chroot); if (group == NULL) { sprintf(numgroupstr, "%d", (int)fileinfo->st_gid); group = numgroupstr; } strcache_add(gidcache, (int)fileinfo->st_gid, group); } group[8] = 0; /* Do symbolic links */ if (permissions[0] == 'l') { char linkname[BUFFERSIZE]; int linklen = readlink(filename, linkname, BUFFERSIZE - 1); if (linklen != -1) { linkname[linklen] = 0; output = safe_snprintf("%s %3d %-8s %-8s %s %12s %s -> %s\r\n", permissions, links, username, group, size, date, filename, linkname); } } if (!output) output = safe_snprintf("%s %3d %-8s %-8s %s %12s %s\r\n", permissions, links, username, group, size, date, filename); } else output = safe_snprintf("---------- 1 unknown unknown 0 Jan 1 00:00 %s\r\n", filename); return(output); } void list_decodesubdirs(LISTHANDLE *lh) { int count; /* no need to do this if no files in this dir */ if (lh->dirdata->namecount == 0) return; reallocwrapper(sizeof(char *) * (lh->dirdata->namecount + lh->numsubdirs), (void *)&(lh->subdirs)); for (count = lh->dirdata->namecount - 1; count >= 0; count--) { if (S_ISDIR(lh->dirdata->stat_buf[count].st_mode)) { if ((strcmp(lh->dirdata->name[count], "..") != 0) && (strcmp(lh->dirdata->name[count], ".") != 0)) { lh->subdirs[lh->numsubdirs] = safe_snprintf("%s/%s", lh->dir, lh->dirdata->name[count]); lh->numsubdirs++; } } } } int buildnextdir(FTPSTATE *peer, LISTHANDLE *lh) { MYGLOBDATA *dirlisting = NULL; myglobfree(lh->dirdata); lh->dirdata = NULL; /* loop while there are still subdirectories to look at until an accessable one is found */ while ((!dirlisting) && (lh->numsubdirs > 0)) { freewrapper(lh->dir); lh->numsubdirs--; lh->dir = lh->subdirs[lh->numsubdirs]; dirlisting = file_glob(peer, lh->dir, "*", lh->all); lh->subdirs[lh->numsubdirs] = NULL; } if (dirlisting) { lh->dirdata = dirlisting; lh->pos = -1; list_decodesubdirs(lh); return(TRUE); } return(FALSE); } LISTHANDLE *getlisthandle(FTPSTATE *peer, char *dirpattern, int nlist, char *reldir, int params) { char *mypat = strdupwrapper(dirpattern); char *dir, *pattern; char *t; time_t ct; LISTHANDLE *lh; /* manufacture original listing dir and pattern */ dir = mypat; *(strrchr(mypat, '/')) = 0; pattern = strrchr(dirpattern, '/') + 1; lh = mallocwrapper(sizeof(LISTHANDLE)); lh->dirdata = file_glob(peer, dir, pattern, params & LISTALL); if (!lh->dirdata) { freewrapper(lh); freewrapper(mypat); return(NULL); } ct = time(NULL); t = ctime(&ct); memcpy(&(lh->year), t + 20, 4); lh->uidcache = strcache_new(); lh->gidcache = strcache_new(); lh->pos = 0; lh->nlist = nlist; lh->rdir = reldir; lh->odirlen = strlen(dir); lh->dir = dir; lh->recursive = params & LISTRECURSE; lh->all = params & LISTALL; lh->numsubdirs = 0; lh->subdirs = NULL; if (lh->recursive) { list_decodesubdirs(lh); lh->pos = -1; } return(lh); } char *getlisthandleline(FTPSTATE *peer, LISTHANDLE *lh) { char *longname; char *ret; char *relativename; if (lh == NULL) return(NULL); if (lh->pos == -1) { relativename = lh->dir + lh->odirlen; if (relativename[0] == 0) { if (lh->rdir[0] == 0) ret = strdupwrapper(".:\r\n"); else ret = safe_snprintf("%s:\r\n", lh->rdir); } else { if (lh->rdir[0] == 0) ret = safe_snprintf("%s:\r\n", relativename + 1); else if ((lh->rdir[0] == '/') && (lh->rdir[1] == 0)) ret = safe_snprintf("%s:\r\n", relativename); else ret = safe_snprintf("%s%s:\r\n", lh->rdir, relativename); } lh->pos++; return(ret); } if (lh->pos == 0) { lh->pos++; if (!(lh->nlist)) return(safe_snprintf("total %d\r\n", lh->dirdata->blocks)); } if (lh->pos > lh->dirdata->namecount) { if (lh->recursive) if (buildnextdir(peer, lh)) return(strdupwrapper("\r\n")); return(NULL); } if (lh->nlist) { ret = safe_snprintf("%s\r\n", lh->dirdata->name[lh->pos-1]); lh->pos++; return(ret); } longname = safe_snprintf("%s/%s", lh->dir, lh->dirdata->name[lh->pos-1]); ret = listmakeline(peer, longname, lh->dirdata->name[lh->pos-1], &(lh->dirdata->stat_buf[lh->pos-1]), lh->uidcache, lh->gidcache, lh->year); lh->pos++; freewrapper(longname); return(ret); } void freelisthandle(LISTHANDLE *lh) { if (!lh) return; while(lh->numsubdirs > 0) { lh->numsubdirs--; freewrapper(lh->subdirs[lh->numsubdirs]); } freeifnotnull(lh->subdirs); if (lh->dirdata) myglobfree(lh->dirdata); freewrapper(lh->dir); freewrapper(lh->rdir); strcache_free(lh->uidcache); strcache_free(lh->gidcache); freewrapper(lh); return; } int list_write(SELECTER *sel, int fd, void *peerv) { FTPSTATE *peer = (FTPSTATE *)peerv; DATAPORT *d = peer->dport; int finish = FALSE; int size = 0; int maxsize; if (STRLENGTH(d->buffer) == 0) { int ret = FALSE; while (!ret) { char *line = getlisthandleline(peer, d->lhandle); if (line) { string_cat(&(d->buffer), line, -1); freewrapper(line); if (STRLENGTH(d->buffer) > BUFFERSIZE) ret = TRUE; } else { ret = TRUE; finish = TRUE; } } } if (STRLENGTH(d->buffer) > 0) { if (peer->maxtranspd_down) maxsize = MINIMUM(peer->maxtranspd_down, STRLENGTH(d->buffer)); else if (peer->maxtranspd) maxsize = MINIMUM(peer->maxtranspd, STRLENGTH(d->buffer)); else maxsize = STRLENGTH(d->buffer); size = write(d->socketfd, STRTOCHAR(d->buffer), maxsize); } if (size > 0) { peer->listdownloadedbytes += size; d->transbytes += size; string_dropfront(&(d->buffer), size); } if ((size <= 0) || (finish && (STRLENGTH(d->buffer) == 0))) { if ((peer->maxtranspd) || (peer->maxtranspd_down)) limiter_add(d->download_limiter, 0, TRUE); socket_flush_wait(d->socketfd, peer->timeout); close(d->socketfd); if (finish) ftp_write(peer, FALSE, 226, REPLY_TRANSDONE(d->transbytes)); else ftp_write(peer, FALSE, 426, REPLY_TRANSABORT(d->transbytes)); closedatasocket(peer); return(2); } if ((peer->maxtranspd) || (peer->maxtranspd_down)) limiter_add(d->download_limiter, size, FALSE); return(FALSE); } char *listmakepattern(FTPSTATE *peer, char *parm, char **rdir, int flags) { char *dir; char *parm2; int l; if (parm == NULL) parm2 = strdupwrapper("*"); else parm2 = strdupwrapper(parm); dir = file_expand(peer, parm2); freewrapper(parm2); if (parm) *rdir = strdupwrapper(parm); else *rdir = strdupwrapper(""); /* This will check for directory names and changes them to the files in the directory instead unless user specifiess -d */ if (checkdir(dir) && !(flags & LISTDIR)) { int a = strlen(dir); reallocwrapper(a + 6, (void *)&dir); dir[a] = '/'; dir[a+1] = '*'; dir[a+2] = 0; } else { char *pos = strrchr(*rdir, '/'); if (pos) *(strrchr(*rdir, '/')) = 0; else **rdir = 0; } l = strlen(*rdir); if (l > 1) while((*rdir)[l - 1] == '/') { (*rdir)[l - 1] = 0; l--; } return(dir); } int ftp_lister(FTPSTATE *peer, char *parm, int nlist, int params) { char *fpattern; char *reldir; /* if -d used, recursion gets turned off */ if ((params & LISTDIR) && (params & LISTRECURSE)) params = params & ~LISTRECURSE; fpattern = listmakepattern(peer, parm, &reldir, params); if (startdatasocket(peer, -1, TRANS_LIST, -1) == 0) { peer->dport->lhandle = getlisthandle(peer, fpattern, nlist, reldir, params); peer->listconns++; } freewrapper(fpattern); return(FALSE); }