/* 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" void file_becomeuser(FTPSTATE *peer) { if (config->rootmode) { if (setegid(peer->gidt_asgid) == -1) log_giveentry(MYLOG_INFO, NULL, safe_snprintf("setegid failed: %s", strerror(errno))); if (seteuid(peer->uidt_asuid) == -1) log_giveentry(MYLOG_INFO, NULL, safe_snprintf("seteuid failed: %s", strerror(errno))); } } void file_becomeroot(FTPSTATE *peer) { if (config->rootmode) { if (seteuid((uid_t)0) == -1) log_giveentry(MYLOG_INFO, NULL, safe_snprintf("seteuid failed: %s", strerror(errno))); if (setegid((gid_t)0) == -1) log_giveentry(MYLOG_INFO, NULL, safe_snprintf("setegid failed: %s", strerror(errno))); } } int checkdir(char *dir) { struct stat check; int result = stat(dir, &check); if (result == 0) if (S_ISDIR(check.st_mode)) return(TRUE); return(FALSE); } int checkfile(char *file) { struct stat check; int result = stat(file, &check); if (result == 0) if (!S_ISDIR(check.st_mode)) return(TRUE); return(FALSE); } int checkchdir(FTPSTATE *peer, char *dir) { int result; char *dir2 = safe_snprintf("%s/", dir); result = (check_acl(peer, dir2, ACL_CHDIR) == 0); if (result) result = (chdir(dir) == 0); freewrapper(dir2); return(result); } /* this needs explaining, send this function a pointer to nine '-' in a string ie "---------" and the file mode of the file, and it will send back the permissions for that file in that string, eg "-rw-r--r--" */ /* this version was inspired by how GNU fileutils does it */ void convertperms(char *permstr, int mode) { int mcpy = mode; int cnt; /* work out the 3 sets of rwx's */ for(cnt = 6; cnt > -1; cnt -= 3) { permstr[cnt+1] = (mcpy & S_IROTH) ? 'r' : '-'; permstr[cnt+2] = (mcpy & S_IWOTH) ? 'w' : '-'; permstr[cnt+3] = (mcpy & S_IXOTH) ? 'x' : '-'; mcpy = (mcpy >> 3); } /* work out special bits. Setuid the setgid the sticky bit */ if (mode & S_ISUID) permstr[3] = (mode & S_IXUSR) ? 's' : 'S'; if (mode & S_ISGID) permstr[6] = (mode & S_IXGRP) ? 's' : 'S'; if (mode & S_ISVTX) permstr[9] = (mode & S_IXOTH) ? 't' : 'T'; /* check for regular files first, better performace! */ if (S_ISREG(mode)) permstr[0] = '-'; else if (S_ISLNK(mode)) permstr[0] = 'l'; else if (S_ISDIR(mode)) permstr[0] = 'd'; else if (S_ISBLK(mode)) permstr[0] = 'b'; else if (S_ISCHR(mode)) permstr[0] = 'c'; else if (S_ISFIFO(mode)) permstr[0] = 'p'; #ifdef S_ISSOCK else if (S_ISSOCK(mode)) permstr[0] = 's'; #endif else permstr[0] = '?'; } char *file_expand(FTPSTATE *peer, char *filename) { char *newfile = strdupwrapper(peer->pwd); dir_combine(peer, &newfile, filename); return(newfile); } int file_isfdregularfile(int fd) { struct stat buf; if (fstat(fd, &buf) == 0) return(S_ISREG(buf.st_mode)); else return(TRUE); /* assume yes! */ } int file_isfdadir(int fd) { struct stat buf; if (fstat(fd, &buf) == 0) return(S_ISDIR(buf.st_mode)); else return(TRUE); /* assume yes, but there is no good reason fstat won't work! */ } /* opens a unique filename, in peers virtual space, returns the real name in rname for logging */ int file_ustoreopen(FTPSTATE *peer, char *infile, int *nounique, char **rname) { char *filename = file_expand(peer, infile); int checknext = TRUE; int pos = 0; int filefd = -1; int epos = strlen(filename); *nounique = FALSE; reallocwrapper(epos + 5, (void **)&filename); if (check_acl(peer, filename, ACL_ADD) == 0) { filefd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666 & ~peer->umask); checknext = ((filefd == -1) && (errno == EEXIST)); } while((checknext) && (pos < 1000)) { filename[epos] = '.'; filename[epos+1] = (pos / 100) + '0'; filename[epos+2] = (pos % 100) / 10 + '0'; filename[epos+3] = (pos % 10) + '0'; filename[epos+4] = 0; pos++; if (check_acl(peer, filename, ACL_ADD) == 0) { filefd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666 & ~peer->umask); checknext = ((filefd == -1) && (errno == EEXIST)); } } if (pos >= 1000) *nounique = TRUE; *rname = filename; return(filefd); } /* opens a file for storing in peers virtual space, returns the real name for logging in rname */ int file_storeopen(FTPSTATE *peer, char *infile, char **rname) { char *filename = file_expand(peer, infile); int newfd = -1; int openflags = FALSE; int canoverwrite = FALSE; int canadd; canoverwrite = (check_acl(peer, filename, ACL_REPLACE) == 0); canadd = (check_acl(peer, filename, ACL_ADD) == 0); /* if the user can add files, then allow creation */ if (canadd) { openflags |= O_CREAT; /* if the user cannot overwrite files */ if (!canoverwrite) openflags |= O_EXCL; } /* try opening the file, or no-op if no acl perms available */ if (canoverwrite || canadd) newfd = open(filename, openflags | O_WRONLY, 0666 & ~peer->umask); else errno = EACCES; /* now test it against device file access settings */ if ((errno == 0) && (!peer->accessdevices)) if (!file_isfdregularfile(newfd)) { close(newfd); errno = EACCES; newfd = -1; } *rname = filename; return(newfd); } int file_readopen(FTPSTATE *peer, char *infile, char **rname) { int filefd = -1; char *filename = file_expand(peer, infile); check_acl(peer, filename, ACL_READ); if (errno == 0) filefd = open(filename, O_RDONLY); if (errno == 0) if (file_isfdadir(filefd)) { close(filefd); errno = EISDIR; filefd = -1; } if ((errno == 0) && (!peer->accessdevices)) if (!file_isfdregularfile(filefd)) { close(filefd); errno = EACCES; filefd = -1; } *rname = filename; return(filefd); } NEWFILE *file_nfopen(FTPSTATE *peer, char *filename) { char *rname; int fd = file_readopen(peer, filename, &rname); NEWFILE *result = NULL; if (fd > 0) result = nfdopen(fd); freewrapper(rname); return(result); } MYGLOBDATA *file_glob(FTPSTATE *peer, char *dir, char *pattern, int allfiles) { int dirlen = strlen(dir); MYGLOBDATA *mg = NULL; char *pdir = mallocwrapper(dirlen + 2); memcpy(pdir, dir, dirlen); if (pdir[dirlen-1] == '/') dirlen--; pdir[dirlen] = '/'; pdir[dirlen+1] = 0; check_acl(peer, pdir, ACL_LIST); if (errno == 0) mg = myglob(pdir, pattern, allfiles, TRUE); freewrapper(pdir); return(mg); } int file_unlink(FTPSTATE *peer, char *infile) { int result = -1; char *filename = file_expand(peer, infile); check_acl(peer, filename, ACL_DELETE); if (errno == 0) result = unlink(filename); freewrapper(filename); return(result); } /* there is a race here, where the user can get to overwrite a file if it created between checkfile and rename. It appears to be very difficult to prevent */ char *file_rename(FTPSTATE *peer, char *infile, char *infile2) { char *filename = file_expand(peer, infile); char *filename2 = file_expand(peer, infile2); char *badfile = NULL; if (checkfile(filename2)) { if (check_acl(peer, filename2, ACL_DELETE) == -1) badfile = infile2; } else errno = 0; if (errno == 0) if (check_acl(peer, filename2, ACL_ADD) == -1) badfile = infile2; if (errno == 0) if (check_acl(peer, filename, ACL_DELETE) == -1) badfile = infile; if (errno == 0) { if (rename(filename, filename2) == -1) badfile = infile; } freewrapper(filename); freewrapper(filename2); return(badfile); } int file_stat(FTPSTATE *peer, char *infile, struct stat *output) { int result = -1; char *filename = file_expand(peer, infile); check_acl(peer, filename, ACL_READ); if (errno == 0) result = stat(filename, output); freewrapper(filename); return(result); } int file_mkdir(FTPSTATE *peer, char *infile) { int result = -1; char *filename = file_expand(peer, infile); check_acl(peer, filename, ACL_MKDIR); if (errno == 0) result = mkdir(filename, 0777 & ~peer->umask); freewrapper(filename); return(result); } int file_rmdir(FTPSTATE *peer, char *infile) { int result = -1; char *filename = file_expand(peer, infile); check_acl(peer, filename, ACL_RMDIR); if (errno == 0) result = rmdir(filename); freewrapper(filename); return(result); } int file_chmod(FTPSTATE *peer, char *infile, int mode) { int result = -1; char *filename = file_expand(peer, infile); check_acl(peer, filename, ACL_CHMOD); if (errno == 0) /* never allow sticky bit or setgid or setuid to be set */ result = chmod(filename, mode & 0777); freewrapper(filename); return(result); }